beanbagdb 0.5.52 → 0.5.54
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/doc-src/3_plugins.md +23 -1
- package/package.json +3 -2
- package/src/index.js +284 -265
- package/src/plugins/text_command.js +193 -0
- package/src/system_schema.js +34 -4
- package/test/couchdb.js +2 -2
- package/test/operations.test.js +1687 -286
- package/test/plugin.test.js +55 -0
- package/test/pouchdb.js +12 -6
- package/test/test1.js +146 -161
package/src/index.js
CHANGED
|
@@ -159,7 +159,7 @@ export class BeanBagDB {
|
|
|
159
159
|
// check for schema_scehma : if yes, check if latest and upgrade if required, if no create a new schema doc
|
|
160
160
|
let logs = ["init started"];
|
|
161
161
|
try {
|
|
162
|
-
let schema = await this.
|
|
162
|
+
let schema = await this.get("schema",{name:"schema"});
|
|
163
163
|
if (schema["data"]["version"] != sys_sch.schema_schema.version) {
|
|
164
164
|
logs.push("old schema_schema v " + schema["data"]["version"]);
|
|
165
165
|
let full_doc = await this.db_api.get(schema["_id"]);
|
|
@@ -169,11 +169,8 @@ export class BeanBagDB {
|
|
|
169
169
|
logs.push("new schema_schema v " + sys_sch.schema_schema.version);
|
|
170
170
|
}
|
|
171
171
|
} catch (error) {
|
|
172
|
-
console.log(error);
|
|
172
|
+
// console.log(error);
|
|
173
173
|
if (error instanceof DocNotFoundError) {
|
|
174
|
-
console.log("iiii")
|
|
175
|
-
//error.message == BeanBagDB.error_codes.schema_not_found) {
|
|
176
|
-
console.log("...adding new ");
|
|
177
174
|
// inserting new schema_schema doc
|
|
178
175
|
let schema_schema_doc = this._get_blank_doc("schema");
|
|
179
176
|
schema_schema_doc.data = sys_sch.schema_schema;
|
|
@@ -188,7 +185,7 @@ export class BeanBagDB {
|
|
|
188
185
|
const schema_data = sys_sch.system_schemas[keys[index]];
|
|
189
186
|
try {
|
|
190
187
|
// console.log(schema_name)
|
|
191
|
-
let schema1 = await this.
|
|
188
|
+
let schema1 = await this.get("schema",{name:schema_name})
|
|
192
189
|
if (schema1["data"]["version"] != schema_data.version) {
|
|
193
190
|
logs.push("old " + schema_name + " v " + schema1["data"]["version"]);
|
|
194
191
|
let full_doc = await this.db_api.get(schema1["_id"]);
|
|
@@ -198,7 +195,7 @@ export class BeanBagDB {
|
|
|
198
195
|
logs.push("new " + schema_name + " v " + schema_data.version);
|
|
199
196
|
}
|
|
200
197
|
} catch (error) {
|
|
201
|
-
console.log(error);
|
|
198
|
+
// console.log(error);
|
|
202
199
|
if (error instanceof DocNotFoundError) {
|
|
203
200
|
// inserting new schema doc
|
|
204
201
|
let new_schema_doc = this._get_blank_schema_doc(
|
|
@@ -227,6 +224,7 @@ export class BeanBagDB {
|
|
|
227
224
|
|
|
228
225
|
this.meta.beanbagdb_version_db = this._version;
|
|
229
226
|
this.active = true;
|
|
227
|
+
console.log(logs.join(","))
|
|
230
228
|
} else {
|
|
231
229
|
// no new updates were done
|
|
232
230
|
console.log("Database already up to date");
|
|
@@ -345,10 +343,12 @@ export class BeanBagDB {
|
|
|
345
343
|
*/
|
|
346
344
|
async create(schema, data, meta = {}, settings = {}) {
|
|
347
345
|
this._check_ready_to_use();
|
|
346
|
+
if(!schema){throw new DocCreationError(`No schema provided`)}
|
|
347
|
+
if(Object.keys(data).length==0){throw new DocCreationError(`No data provided`)}
|
|
348
348
|
try {
|
|
349
|
-
let doc_obj = await this._insert_pre_checks(schema, data, settings);
|
|
349
|
+
let doc_obj = await this._insert_pre_checks(schema, data,meta, settings);
|
|
350
350
|
let new_rec = await this.db_api.insert(doc_obj);
|
|
351
|
-
return {
|
|
351
|
+
return {_id:new_rec["id"],_rev : new_rec["rev"] ,...doc_obj};
|
|
352
352
|
} catch (error) {
|
|
353
353
|
throw error;
|
|
354
354
|
}
|
|
@@ -379,26 +379,41 @@ export class BeanBagDB {
|
|
|
379
379
|
*/
|
|
380
380
|
async read(criteria, include_schema = false) {
|
|
381
381
|
// todo : decrypt doc
|
|
382
|
-
this._check_ready_to_use()
|
|
383
|
-
let obj = { doc:
|
|
382
|
+
this._check_ready_to_use()
|
|
383
|
+
let obj = { doc: null }
|
|
384
|
+
let data_schema = null
|
|
384
385
|
if (criteria._id) {
|
|
385
|
-
|
|
386
|
-
|
|
386
|
+
try {
|
|
387
|
+
let doc = await this.db_api.get(criteria._id)
|
|
388
|
+
//console.log(doc)
|
|
389
|
+
obj.doc = doc;
|
|
390
|
+
} catch (error) { throw new DocNotFoundError(BeanBagDB.error_codes.doc_not_found)}
|
|
387
391
|
} else if (criteria.link) {
|
|
388
|
-
let linkSearch = await this.db_api.search({selector: { "meta.link": criteria.link }
|
|
389
|
-
if (linkSearch.docs.length == 0) {throw new DocNotFoundError(BeanBagDB.error_codes.doc_not_found)
|
|
392
|
+
let linkSearch = await this.db_api.search({selector: { "meta.link": criteria.link }})
|
|
393
|
+
if (linkSearch.docs.length == 0) {throw new DocNotFoundError(BeanBagDB.error_codes.doc_not_found)}
|
|
390
394
|
obj.doc = linkSearch.docs[0];
|
|
391
|
-
} else if (criteria.schema & criteria.data) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
+
} else if (criteria.hasOwnProperty("schema") & criteria.hasOwnProperty("data")) {
|
|
396
|
+
data_schema = await this.get("schema",{"name":criteria.schema})
|
|
397
|
+
let A = data_schema["data"]["settings"]["primary_keys"];
|
|
398
|
+
let search_criteria = { schema: criteria.schema };
|
|
399
|
+
A.forEach((itm) => {
|
|
400
|
+
if (!criteria["data"][itm]) {throw new ValidationError("Incomplete Primary key set. Required field(s) : " + A.join(","))}
|
|
401
|
+
search_criteria["data." + itm] = criteria["data"][itm];
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
let pkSearch = await this.db_api.search({selector: search_criteria})
|
|
405
|
+
if (pkSearch.docs.length == 0) {throw new DocNotFoundError(BeanBagDB.error_codes.doc_not_found)}
|
|
406
|
+
obj.doc = pkSearch.docs[0]
|
|
407
|
+
|
|
395
408
|
} else {
|
|
396
|
-
throw new ValidationError(`Invalid criteria to read a document. Valid ways : {"schema":"schema_name","data":{...primary key}} or {"_id":""} or {"link":""} `)
|
|
409
|
+
throw new ValidationError(`Invalid criteria to read a document. Valid ways : {"schema":"schema_name","data":{...primary key}} or {"_id":""} or {"link":""} `)
|
|
397
410
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
obj.schema = await this.get_schema(obj.doc.schema);
|
|
411
|
+
if (!data_schema){
|
|
412
|
+
data_schema = await this.get("schema",{"name":obj.doc.schema})
|
|
401
413
|
}
|
|
414
|
+
if(include_schema) {obj.schema = data_schema["data"]}
|
|
415
|
+
// decrypt the document
|
|
416
|
+
obj.doc = await this._decrypt_doc(data_schema["data"], obj.doc)
|
|
402
417
|
return obj;
|
|
403
418
|
}
|
|
404
419
|
|
|
@@ -439,20 +454,13 @@ export class BeanBagDB {
|
|
|
439
454
|
* @throws {DocUpdateError} - If a document with conflicting primary keys already exists.
|
|
440
455
|
* @throws {ValidationError} - If the provided data or metadata is invalid according to the schema.
|
|
441
456
|
*/
|
|
442
|
-
async update(
|
|
443
|
-
doc_search_criteria,
|
|
444
|
-
rev_id,
|
|
445
|
-
updates,
|
|
446
|
-
update_source = "api",
|
|
447
|
-
save_conflict = true
|
|
448
|
-
) {
|
|
457
|
+
async update(doc_search_criteria, updates, rev_id="", update_source = "api", save_conflict = true) {
|
|
449
458
|
this._check_ready_to_use();
|
|
450
459
|
// making a big assumption here : primary key fields cannot be edited
|
|
451
460
|
// so updating the doc will not generate primary key conflicts
|
|
452
461
|
let req_data = await this.read(doc_search_criteria, true);
|
|
453
462
|
let schema = req_data.schema;
|
|
454
463
|
let full_doc = req_data.doc;
|
|
455
|
-
|
|
456
464
|
// @TODO fix this : what to do if the rev id does not match
|
|
457
465
|
// if (full_doc["_rev"] != rev_id) {
|
|
458
466
|
// // throw error , save conflicting doc separately by default
|
|
@@ -464,50 +472,69 @@ export class BeanBagDB {
|
|
|
464
472
|
// update new value depending on settings.non_editable_fields (if does not exists, all fields are editable)
|
|
465
473
|
let all_fields = Object.keys(schema.schema.properties);
|
|
466
474
|
let unedit_fields = schema.settings["non_editable_fields"];
|
|
467
|
-
let edit_fields = all_fields.filter(
|
|
468
|
-
(item) => !unedit_fields.includes(item)
|
|
469
|
-
);
|
|
470
|
-
|
|
475
|
+
let edit_fields = all_fields.filter((item) => !unedit_fields.includes(item))
|
|
471
476
|
// now generate the new doc with updates
|
|
472
|
-
let
|
|
473
|
-
let
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
477
|
+
let something_to_update = false
|
|
478
|
+
let allowed_updates = this.util_filter_object(updates.data||{}, edit_fields);
|
|
479
|
+
if(Object.keys(allowed_updates).length>0){
|
|
480
|
+
// todo : what if additionalField are allowed ??
|
|
481
|
+
let updated_data = { ...full_doc.data, ...allowed_updates };
|
|
482
|
+
|
|
483
|
+
this.util_validate_data(schema.schema, updated_data);
|
|
484
|
+
|
|
485
|
+
// primary key check if multiple records can be created
|
|
486
|
+
if (schema.settings["primary_keys"].length > 0) {
|
|
487
|
+
let pri_fields = schema.settings["primary_keys"];
|
|
488
|
+
let search_criteria = { schema: schema.name };
|
|
489
|
+
pri_fields.map((itm) => {search_criteria["data." + itm] = updated_data[itm];});
|
|
490
|
+
let search = await this.search({ selector: search_criteria });
|
|
491
|
+
if (search.docs.length > 0) {
|
|
492
|
+
if (search.docs.length == 1) {
|
|
493
|
+
let thedoc = search.docs[0];
|
|
494
|
+
if (thedoc["_id"] != full_doc._id) {
|
|
495
|
+
throw new DocUpdateError("Update not allowed. Document with the same primary key already exists");
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
throw new Error("There is something wrong with the schema primary keys");
|
|
488
499
|
}
|
|
489
|
-
} else {
|
|
490
|
-
throw new Error("There is something wrong with the schema primary keys");
|
|
491
500
|
}
|
|
492
501
|
}
|
|
502
|
+
|
|
503
|
+
full_doc["data"] = updated_data;
|
|
504
|
+
something_to_update = true
|
|
493
505
|
}
|
|
494
|
-
|
|
495
|
-
// encrypt the data
|
|
496
|
-
full_doc["data"] = updated_data;
|
|
497
|
-
full_doc = this._encrypt_doc(schema, full_doc);
|
|
498
|
-
|
|
499
506
|
if (updates.meta) {
|
|
500
507
|
let m_sch = sys_sch.editable_metadata_schema;
|
|
501
508
|
let editable_fields = Object.keys(m_sch["properties"]);
|
|
502
509
|
let allowed_meta = this.util_filter_object(updates.meta, editable_fields);
|
|
503
510
|
this.util_validate_data(m_sch, allowed_meta);
|
|
511
|
+
// if update has a link ,then check if it already exists
|
|
512
|
+
if (allowed_meta.link){
|
|
513
|
+
let search = await this.search({ selector: {"meta.link":allowed_meta.link} })
|
|
514
|
+
if (search.docs.length > 0) {
|
|
515
|
+
if (search.docs.length == 1) {
|
|
516
|
+
let thedoc = search.docs[0];
|
|
517
|
+
if (thedoc["_id"] != full_doc._id) {
|
|
518
|
+
throw new DocUpdateError("Update not allowed. Document with the same link already exists");
|
|
519
|
+
}
|
|
520
|
+
} else {throw new Error("There is something wrong.")}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
504
524
|
full_doc["meta"] = { ...full_doc["meta"], ...allowed_meta };
|
|
525
|
+
something_to_update = true
|
|
526
|
+
}
|
|
527
|
+
if(something_to_update){
|
|
528
|
+
// encrypt the data again since read returns decrypted data
|
|
529
|
+
full_doc = await this._encrypt_doc(schema, full_doc);
|
|
530
|
+
full_doc.meta["updated_on"] = this.util_get_now_unix_timestamp();
|
|
531
|
+
full_doc.meta["updated_by"] = update_source;
|
|
532
|
+
// console.log(full_doc)
|
|
533
|
+
let up = await this.db_api.update(full_doc);
|
|
534
|
+
return up;
|
|
535
|
+
}else{
|
|
536
|
+
throw new DocUpdateError("Nothing to update")
|
|
505
537
|
}
|
|
506
|
-
|
|
507
|
-
full_doc.meta["updated_on"] = this.util_get_now_unix_timestamp();
|
|
508
|
-
full_doc.meta["updated_by"] = update_source;
|
|
509
|
-
let up = await this.db_api.update(full_doc);
|
|
510
|
-
return up;
|
|
511
538
|
}
|
|
512
539
|
|
|
513
540
|
|
|
@@ -518,9 +545,14 @@ export class BeanBagDB {
|
|
|
518
545
|
* @throws {DocNotFoundError} If the document with the specified ID does not exist.
|
|
519
546
|
* @throws {ValidationError} If the database is not ready to use.
|
|
520
547
|
*/
|
|
521
|
-
async delete(
|
|
548
|
+
async delete(criteria) {
|
|
522
549
|
this._check_ready_to_use();
|
|
523
|
-
await this.
|
|
550
|
+
let doc = await this.read(criteria)
|
|
551
|
+
const delete_blocked = ["schema","setting","key"]
|
|
552
|
+
if (delete_blocked.includes(doc.schema)){
|
|
553
|
+
throw new Error(`Deletion of ${doc.schema} doc is not support yet.`)
|
|
554
|
+
}
|
|
555
|
+
await this.db_api.delete(doc.doc._id);
|
|
524
556
|
}
|
|
525
557
|
|
|
526
558
|
|
|
@@ -535,85 +567,86 @@ export class BeanBagDB {
|
|
|
535
567
|
* E.g
|
|
536
568
|
* @param {Object} criteria
|
|
537
569
|
*/
|
|
538
|
-
async search(criteria) {
|
|
570
|
+
async search(criteria={}) {
|
|
539
571
|
this._check_ready_to_use();
|
|
540
572
|
if (!criteria["selector"]) {
|
|
541
|
-
throw new
|
|
542
|
-
}
|
|
543
|
-
if (!criteria["selector"]["schema"]) {
|
|
544
|
-
throw new Error("The search criteria must contain the schema");
|
|
573
|
+
throw new ValidationError("Invalid search query.Use {selector:{...query...}}");
|
|
545
574
|
}
|
|
575
|
+
//if (!criteria["selector"]["schema"]) {
|
|
576
|
+
// throw new Error("The search criteria must contain the schema");
|
|
577
|
+
//}
|
|
546
578
|
const results = await this.db_api.search(criteria);
|
|
547
579
|
return results;
|
|
548
580
|
}
|
|
549
581
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
let s_doc = schema_doc["data"];
|
|
577
|
-
let doc_obj;
|
|
578
|
-
if (
|
|
579
|
-
s_doc["settings"]["primary_keys"] &&
|
|
580
|
-
s_doc["settings"]["primary_keys"].length > 0
|
|
581
|
-
) {
|
|
582
|
-
let A = s_doc["settings"]["primary_keys"];
|
|
583
|
-
let search_criteria = { schema: schema_name };
|
|
584
|
-
A.forEach((itm) => {
|
|
585
|
-
if (!primary_key[itm]) {
|
|
586
|
-
throw new Error(
|
|
587
|
-
"Incomplete Primary key set. Required field(s) : " + A.join(",")
|
|
588
|
-
);
|
|
582
|
+
/**
|
|
583
|
+
* Retrieves special types of documents from the database, such as schema documents or blank documents
|
|
584
|
+
* for a given schema. It handles system-related data and throws errors for invalid document types
|
|
585
|
+
* or if the document is not found.
|
|
586
|
+
*
|
|
587
|
+
* @param {String} special_doc_type - The type of special document to fetch. Supported types include:
|
|
588
|
+
* - 'schema': Retrieves a schema document based on the criteria provided.
|
|
589
|
+
* @param {Object} [criteria={}] - Criteria used to search for the special document.
|
|
590
|
+
* For example, to search for a schema, the criteria should include the name.
|
|
591
|
+
*
|
|
592
|
+
* @throws {ValidationError} Throws if the `special_doc_type` is not recognized.
|
|
593
|
+
* @throws {DocNotFoundError} Throws if the requested document is not found in the database.
|
|
594
|
+
*
|
|
595
|
+
* @returns {Object} The fetched special document based on the type and criteria.
|
|
596
|
+
*/
|
|
597
|
+
async get(special_doc_type,criteria={}){
|
|
598
|
+
// this method returns special types of documents such as schema doc, or a blank doc for a given schema and other system related things
|
|
599
|
+
const fetch_docs = {
|
|
600
|
+
// to return schema object for the given name
|
|
601
|
+
schema:async (criteria)=>{
|
|
602
|
+
let schemaSearch = await this.db_api.search({
|
|
603
|
+
selector: { schema: "schema", "data.name": criteria.name },
|
|
604
|
+
});
|
|
605
|
+
// console.log(schemaSearch)
|
|
606
|
+
if (schemaSearch.docs.length == 0) {
|
|
607
|
+
throw new DocNotFoundError(BeanBagDB.error_codes.schema_not_found);
|
|
589
608
|
}
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
609
|
+
return schemaSearch.docs[0];
|
|
610
|
+
},
|
|
611
|
+
// schema list
|
|
612
|
+
schema_list:async (criteria)=>{
|
|
613
|
+
let schemaSearch = await this.db_api.search({
|
|
614
|
+
selector: { schema: "schema" },
|
|
615
|
+
});
|
|
616
|
+
// console.log(schemaSearch)
|
|
617
|
+
if (schemaSearch.docs.length == 0) {
|
|
618
|
+
throw new DocNotFoundError(BeanBagDB.error_codes.schema_not_found);
|
|
619
|
+
}else{
|
|
620
|
+
let schemas = []
|
|
621
|
+
schemaSearch.docs.map(doc=>{
|
|
622
|
+
schemas.push({
|
|
623
|
+
name: doc.data.name,
|
|
624
|
+
version: doc.data.version,
|
|
625
|
+
system_defined : doc.data.system_generated,
|
|
626
|
+
description: doc.data.description,
|
|
627
|
+
link: doc.meta.link,
|
|
628
|
+
_id:doc._id
|
|
629
|
+
})
|
|
630
|
+
})
|
|
631
|
+
return schemas
|
|
632
|
+
}
|
|
633
|
+
|
|
600
634
|
}
|
|
601
|
-
doc_obj = s.docs[0];
|
|
602
635
|
}
|
|
603
|
-
|
|
604
|
-
|
|
636
|
+
if(Object.keys(fetch_docs).includes(special_doc_type)){
|
|
637
|
+
let data = await fetch_docs[special_doc_type](criteria)
|
|
638
|
+
return data
|
|
639
|
+
}else{
|
|
640
|
+
throw new ValidationError("Invalid special doc type. Must be : "+Object.keys(fetch_docs).join(","))
|
|
641
|
+
}
|
|
605
642
|
}
|
|
606
643
|
|
|
607
|
-
|
|
608
644
|
async load_plugin(plugin_name, plugin_module) {
|
|
609
645
|
this._check_ready_to_use();
|
|
610
646
|
this.plugins[plugin_name] = {};
|
|
611
647
|
for (let func_name in plugin_module) {
|
|
612
648
|
if (typeof plugin_module[func_name] == "function") {
|
|
613
|
-
this.plugins[plugin_name][func_name] = plugin_module[func_name].bind(
|
|
614
|
-
null,
|
|
615
|
-
this
|
|
616
|
-
);
|
|
649
|
+
this.plugins[plugin_name][func_name] = plugin_module[func_name].bind(null,this)
|
|
617
650
|
}
|
|
618
651
|
}
|
|
619
652
|
// Check if the plugin has an on_load method and call it
|
|
@@ -626,10 +659,15 @@ export class BeanBagDB {
|
|
|
626
659
|
//////////////// Internal methods ////////////////////////
|
|
627
660
|
//////////////////////////////////////////////////////////
|
|
628
661
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
662
|
+
/**
|
|
663
|
+
* Retrieves the current version of the system by summing up the version numbers
|
|
664
|
+
* of all system-defined schemas.
|
|
665
|
+
*
|
|
666
|
+
* @private
|
|
667
|
+
* @returns {number} The total sum of the version numbers of all system-defined schemas.
|
|
668
|
+
*
|
|
669
|
+
* @throws {Error} Throws if there is an issue calculating the sum of version numbers.
|
|
670
|
+
*/
|
|
633
671
|
_get_current_version() {
|
|
634
672
|
// current version is the sum of versions of all system defined schemas
|
|
635
673
|
let sum = sys_sch.schema_schema.version;
|
|
@@ -681,7 +719,7 @@ export class BeanBagDB {
|
|
|
681
719
|
}
|
|
682
720
|
|
|
683
721
|
/**
|
|
684
|
-
* Generates a blank schema doc ready to be inserted to the database.
|
|
722
|
+
* Generates a blank schema doc ready to be inserted to the database. This is for internal use
|
|
685
723
|
* @private
|
|
686
724
|
* @param {string} schema_name
|
|
687
725
|
* @param {Object} schema_object
|
|
@@ -702,19 +740,17 @@ export class BeanBagDB {
|
|
|
702
740
|
* @param {Object} doc_obj
|
|
703
741
|
* @returns {Object}
|
|
704
742
|
*/
|
|
705
|
-
_decrypt_doc(schema_obj, doc_obj) {
|
|
706
|
-
|
|
707
|
-
schema_obj.settings["encrypted_fields"]
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
);
|
|
715
|
-
});
|
|
743
|
+
async _decrypt_doc(schema_obj, doc_obj) {
|
|
744
|
+
try {
|
|
745
|
+
for (let itm of schema_obj.settings["encrypted_fields"]) {
|
|
746
|
+
doc_obj.data[itm] = await this.utils.decrypt(doc_obj.data[itm], this.encryption_key);
|
|
747
|
+
}
|
|
748
|
+
return { ...doc_obj };
|
|
749
|
+
} catch (error) {
|
|
750
|
+
console.log(error)
|
|
751
|
+
throw new EncryptionError([{message:error.message}])
|
|
716
752
|
}
|
|
717
|
-
|
|
753
|
+
|
|
718
754
|
}
|
|
719
755
|
|
|
720
756
|
/**
|
|
@@ -724,20 +760,18 @@ export class BeanBagDB {
|
|
|
724
760
|
* @param {Object} doc_obj
|
|
725
761
|
* @returns {Object}
|
|
726
762
|
*/
|
|
727
|
-
_encrypt_doc(schema_obj, doc_obj) {
|
|
728
|
-
|
|
729
|
-
schema_obj.settings["encrypted_fields"]
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
});
|
|
763
|
+
async _encrypt_doc(schema_obj, doc_obj) {
|
|
764
|
+
try {
|
|
765
|
+
if (schema_obj.settings["encrypted_fields"].length > 0) {
|
|
766
|
+
// console.log(schema_obj,doc_obj)
|
|
767
|
+
for (let itm of schema_obj.settings["encrypted_fields"]) {
|
|
768
|
+
doc_obj.data[itm] = await this.utils.encrypt(doc_obj.data[itm], this.encryption_key);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return { ...doc_obj };
|
|
772
|
+
} catch (error) {
|
|
773
|
+
throw new EncryptionError([{message:error.message}])
|
|
739
774
|
}
|
|
740
|
-
return { ...doc_obj };
|
|
741
775
|
}
|
|
742
776
|
|
|
743
777
|
/**
|
|
@@ -762,12 +796,8 @@ export class BeanBagDB {
|
|
|
762
796
|
*/
|
|
763
797
|
async _insert_pre_checks(schema, data, meta = {}, settings = {}) {
|
|
764
798
|
// schema search
|
|
765
|
-
let sch_search = await this.search({
|
|
766
|
-
|
|
767
|
-
});
|
|
768
|
-
if (sch_search.docs.length == 0) {
|
|
769
|
-
throw new Error("Invalid Schema");
|
|
770
|
-
}
|
|
799
|
+
let sch_search = await this.search({selector: { schema: "schema", "data.name": schema }})
|
|
800
|
+
if (sch_search.docs.length == 0) {throw new DocCreationError(`The schema "${schema}" does not exists`)}
|
|
771
801
|
let schemaDoc = sch_search.docs[0]["data"];
|
|
772
802
|
// validate data
|
|
773
803
|
this.util_validate_data(schemaDoc.schema, data);
|
|
@@ -777,13 +807,8 @@ export class BeanBagDB {
|
|
|
777
807
|
|
|
778
808
|
// duplicate meta.link check
|
|
779
809
|
if (meta.link) {
|
|
780
|
-
let link_search = await this.search({
|
|
781
|
-
|
|
782
|
-
});
|
|
783
|
-
console.log(link_search);
|
|
784
|
-
if (link_search.docs.length > 0) {
|
|
785
|
-
throw new Error("This link already exists in the database");
|
|
786
|
-
}
|
|
810
|
+
let link_search = await this.search({selector: { "meta.link": meta.link }})
|
|
811
|
+
if (link_search.docs.length > 0) {throw new DocCreationError(`Document with the link "${meta.link}" already exists in the Database.`)}
|
|
787
812
|
}
|
|
788
813
|
|
|
789
814
|
// special checks for special docs
|
|
@@ -793,36 +818,32 @@ export class BeanBagDB {
|
|
|
793
818
|
this.util_validate_schema_object(data);
|
|
794
819
|
}
|
|
795
820
|
// @TODO : check if single record setting is set to true
|
|
796
|
-
|
|
821
|
+
//console.log(schemaDoc)
|
|
797
822
|
// duplicate check
|
|
798
|
-
if (
|
|
799
|
-
schemaDoc.settings["primary_keys"] &&
|
|
800
|
-
schemaDoc.settings["primary_keys"].length > 0
|
|
801
|
-
) {
|
|
823
|
+
if (schemaDoc.settings["primary_keys"].length > 0) {
|
|
802
824
|
let primary_obj = { schema: schema };
|
|
803
|
-
schemaDoc.settings["primary_keys"].map((ky) => {
|
|
804
|
-
primary_obj["data." + ky] = data[ky];
|
|
805
|
-
});
|
|
806
|
-
console.log(primary_obj);
|
|
825
|
+
schemaDoc.settings["primary_keys"].map((ky) => {primary_obj["data." + ky] = data[ky];});
|
|
807
826
|
let prim_search = await this.search({ selector: primary_obj });
|
|
808
|
-
console.log(prim_search);
|
|
809
827
|
if (prim_search.docs.length > 0) {
|
|
810
|
-
throw new
|
|
828
|
+
throw new DocCreationError(`Document with the given primary key (${schemaDoc.settings["primary_keys"].join(",")}) already exists in the schema "${schema}"`);
|
|
811
829
|
}
|
|
812
830
|
}
|
|
813
831
|
// encrypt if required
|
|
814
832
|
let new_data = { ...data };
|
|
815
|
-
if (
|
|
816
|
-
|
|
817
|
-
schemaDoc.settings["encrypted_fields"]
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
new_data[itm] = this.utils.encrypt(data[itm], this.encryption_key);
|
|
821
|
-
});
|
|
833
|
+
if (schemaDoc.settings["encrypted_fields"].length > 0) {
|
|
834
|
+
// todo test if encryption is successful
|
|
835
|
+
for (let itm of schemaDoc.settings["encrypted_fields"]) {
|
|
836
|
+
new_data[itm] = await this.utils.encrypt(data[itm], this.encryption_key);
|
|
837
|
+
}
|
|
822
838
|
}
|
|
839
|
+
|
|
823
840
|
// generate the doc object for data
|
|
824
841
|
let doc_obj = this._get_blank_doc(schema);
|
|
825
842
|
doc_obj["data"] = new_data;
|
|
843
|
+
// if meta exists
|
|
844
|
+
if(meta){
|
|
845
|
+
doc_obj["meta"] = {...doc_obj["meta"],...meta};
|
|
846
|
+
}
|
|
826
847
|
return doc_obj;
|
|
827
848
|
}
|
|
828
849
|
|
|
@@ -830,16 +851,13 @@ export class BeanBagDB {
|
|
|
830
851
|
////// Utility methods /////////////////////////////////////////////////////////////////////////////
|
|
831
852
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
832
853
|
|
|
833
|
-
|
|
834
854
|
/**
|
|
835
855
|
* Returns the current Unix timestamp in seconds.
|
|
836
856
|
* divide by 1000 (Date.now gives ms) to convert to seconds. 1 s = 1000 ms
|
|
837
857
|
* @private
|
|
838
858
|
* @returns {number}
|
|
839
859
|
*/
|
|
840
|
-
util_get_now_unix_timestamp() {
|
|
841
|
-
return Math.floor(Date.now() / 1000);
|
|
842
|
-
}
|
|
860
|
+
util_get_now_unix_timestamp() {return Math.floor(Date.now() / 1000)}
|
|
843
861
|
|
|
844
862
|
/**
|
|
845
863
|
* Validates that the required fields are present in the provided object.
|
|
@@ -850,11 +868,7 @@ export class BeanBagDB {
|
|
|
850
868
|
*/
|
|
851
869
|
util_check_required_fields(requiredFields, obj) {
|
|
852
870
|
for (const field of requiredFields) {
|
|
853
|
-
if (!obj[field]) {
|
|
854
|
-
throw new ValidationError([
|
|
855
|
-
{ message: `The field ${field} is required.` },
|
|
856
|
-
]);
|
|
857
|
-
}
|
|
871
|
+
if (!obj[field]) {throw new ValidationError(`The field ${field} is required.`)}
|
|
858
872
|
}
|
|
859
873
|
}
|
|
860
874
|
|
|
@@ -890,13 +904,7 @@ export class BeanBagDB {
|
|
|
890
904
|
* @throws {Error} If the data object does not conform to the schema
|
|
891
905
|
*/
|
|
892
906
|
util_validate_data(schema_obj, data_obj) {
|
|
893
|
-
const { valid, validate } = this.utils.validate_schema(
|
|
894
|
-
schema_obj,
|
|
895
|
-
data_obj
|
|
896
|
-
);
|
|
897
|
-
//const ajv = new Ajv({code: {esm: true}}) // options can be passed, e.g. {allErrors: true}
|
|
898
|
-
//const validate = ajv.compile(schema_obj);
|
|
899
|
-
//const valid = validate(data_obj);
|
|
907
|
+
const { valid, validate } = this.utils.validate_schema(schema_obj,data_obj)
|
|
900
908
|
if (!valid) {
|
|
901
909
|
throw new ValidationError(validate.errors);
|
|
902
910
|
}
|
|
@@ -932,15 +940,10 @@ export class BeanBagDB {
|
|
|
932
940
|
util_validate_schema_object(schema_doc) {
|
|
933
941
|
let errors = [{ message: "Schema validation errors " }];
|
|
934
942
|
if (!schema_doc["schema"]["type"]) {
|
|
935
|
-
errors.push({
|
|
936
|
-
message:
|
|
937
|
-
"Schema must have the field schema.'type' which can only be 'object' ",
|
|
938
|
-
});
|
|
943
|
+
errors.push({message:"Schema must have the field schema.'type' which can only be 'object' "});
|
|
939
944
|
} else {
|
|
940
945
|
if (schema_doc["schema"]["type"] != "object") {
|
|
941
|
-
errors.push({
|
|
942
|
-
message: "The schema.'type' value is invalid.Only 'object' allowed",
|
|
943
|
-
});
|
|
946
|
+
errors.push({message: "The schema.'type' value is invalid.Only 'object' allowed",});
|
|
944
947
|
}
|
|
945
948
|
}
|
|
946
949
|
if (!schema_doc["schema"]["properties"]) {
|
|
@@ -949,26 +952,18 @@ export class BeanBagDB {
|
|
|
949
952
|
});
|
|
950
953
|
} else {
|
|
951
954
|
if (typeof schema_doc["schema"]["properties"] != "object") {
|
|
952
|
-
errors.push({
|
|
953
|
-
message:
|
|
954
|
-
"Invalid schema.properties. It must be an object and must have atleast one field inside.",
|
|
955
|
-
});
|
|
955
|
+
errors.push({message:"Invalid schema.properties. It must be an object and must have atleast one field inside.",});
|
|
956
956
|
}
|
|
957
957
|
if (Object.keys(schema_doc["schema"]["properties"]).length == 0) {
|
|
958
958
|
errors.push({ message: "You must define at least one property" });
|
|
959
959
|
}
|
|
960
960
|
}
|
|
961
961
|
|
|
962
|
-
if (!schema_doc["schema"]
|
|
963
|
-
errors.push({
|
|
964
|
-
message: "The schema.'additionalProperties' field is required",
|
|
965
|
-
});
|
|
962
|
+
if (!schema_doc["schema"].hasOwnProperty("additionalProperties")) {
|
|
963
|
+
errors.push({message: "The schema.'additionalProperties' field is required",});
|
|
966
964
|
} else {
|
|
967
965
|
if (typeof schema_doc["schema"]["additionalProperties"] != "boolean") {
|
|
968
|
-
errors.push({
|
|
969
|
-
message:
|
|
970
|
-
"Invalid schema.additionalProperties. It must be a boolean value",
|
|
971
|
-
});
|
|
966
|
+
errors.push({message:"Invalid schema.additionalProperties. It must be a boolean value",});
|
|
972
967
|
}
|
|
973
968
|
}
|
|
974
969
|
|
|
@@ -983,10 +978,7 @@ export class BeanBagDB {
|
|
|
983
978
|
);
|
|
984
979
|
|
|
985
980
|
if (!all_pk_exist) {
|
|
986
|
-
errors.push({
|
|
987
|
-
message:
|
|
988
|
-
"Primary keys invalid. All keys must be defined in the schema and must be non object",
|
|
989
|
-
});
|
|
981
|
+
errors.push({message:"Primary keys invalid. All keys must be defined in the schema and must be non object",});
|
|
990
982
|
}
|
|
991
983
|
}
|
|
992
984
|
|
|
@@ -996,63 +988,43 @@ export class BeanBagDB {
|
|
|
996
988
|
(item) => allKeys.includes(item)
|
|
997
989
|
);
|
|
998
990
|
if (!all_ne_exist) {
|
|
999
|
-
errors.push({
|
|
1000
|
-
message:
|
|
1001
|
-
"Non editable fields invalid. All fields must be defined in the schema ",
|
|
1002
|
-
});
|
|
991
|
+
errors.push({message:"Non editable fields invalid. All fields must be defined in the schema ",});
|
|
1003
992
|
}
|
|
1004
993
|
}
|
|
1005
994
|
|
|
1006
995
|
if (schema_doc["settings"]["encrypted_fields"].length > 0) {
|
|
1007
996
|
// check if all keys belong to the schema and are only string
|
|
1008
|
-
let all_enc_exist = schema_doc["settings"]["encrypted_fields"].every(
|
|
1009
|
-
(item) =>
|
|
1010
|
-
allKeys.includes(item) &&
|
|
1011
|
-
schema_doc["schema"]["properties"][item]["type"] == "string"
|
|
1012
|
-
);
|
|
997
|
+
let all_enc_exist = schema_doc["settings"]["encrypted_fields"].every((item) =>allKeys.includes(item) &&schema_doc["schema"]["properties"][item]["type"] == "string");
|
|
1013
998
|
if (!all_enc_exist) {
|
|
1014
|
-
errors.push({
|
|
1015
|
-
message:
|
|
1016
|
-
"Invalid encrypted fields. All fields must be defined in the schema and must be string ",
|
|
1017
|
-
});
|
|
999
|
+
errors.push({message:"Invalid encrypted fields. All fields must be defined in the schema and must be string ",});
|
|
1018
1000
|
}
|
|
1019
1001
|
|
|
1020
1002
|
// check : primary keys cannot be encrypted
|
|
1021
|
-
let all_enc_no_pk = schema_doc["settings"]["encrypted_fields"].every(
|
|
1022
|
-
(item) => !schema_doc["settings"]["primary_keys"].includes(item)
|
|
1023
|
-
);
|
|
1003
|
+
let all_enc_no_pk = schema_doc["settings"]["encrypted_fields"].every((item) => !schema_doc["settings"]["primary_keys"].includes(item));
|
|
1024
1004
|
if (!all_enc_no_pk) {
|
|
1025
|
-
errors.push({
|
|
1026
|
-
message:
|
|
1027
|
-
"Invalid encrypted fields.Primary key fields cannot be encrypted ",
|
|
1028
|
-
});
|
|
1005
|
+
errors.push({message:"Invalid encrypted fields.Primary key fields cannot be encrypted ",});
|
|
1029
1006
|
}
|
|
1030
1007
|
}
|
|
1031
1008
|
|
|
1032
1009
|
/// cannot encrypt primary field keys
|
|
1033
|
-
if (errors.length > 1) {
|
|
1034
|
-
throw new ValidationError(errors);
|
|
1035
|
-
}
|
|
1010
|
+
if (errors.length > 1) {throw new ValidationError(errors)}
|
|
1036
1011
|
}
|
|
1037
1012
|
|
|
1038
|
-
|
|
1013
|
+
/**
|
|
1039
1014
|
* Generates a random link composed of four words from a predefined dictionary.
|
|
1040
1015
|
*
|
|
1041
1016
|
* The words are selected randomly, and the resulting link is formatted as
|
|
1042
1017
|
* a hyphen-separated string. This can be useful for creating link for documents.
|
|
1043
1018
|
*
|
|
1044
|
-
* @returns {String} A hyphen-separated string containing
|
|
1019
|
+
* @returns {String} A hyphen-separated string containing three randomly
|
|
1045
1020
|
* selected words from the dictionary. For example:
|
|
1046
|
-
* "banana-earth-
|
|
1021
|
+
* "banana-earth-rain".
|
|
1047
1022
|
*
|
|
1048
1023
|
*/
|
|
1049
1024
|
util_generate_random_link() {
|
|
1050
1025
|
// prettier-ignore
|
|
1051
1026
|
const dictionary = ['rain', 'mars', 'banana', 'earth', 'kiwi', 'mercury', 'fuji', 'hurricane', 'matterhorn', 'snow', 'saturn', 'jupiter', 'peach', 'wind', 'pluto', 'apple', 'k2', 'storm', 'venus', 'denali', 'cloud', 'sunshine', 'mango', 'drizzle', 'pineapple', 'aconcagua', 'gasherbrum', 'apricot', 'neptune', 'fog', 'orange', 'blueberry', 'kilimanjaro', 'uranus', 'grape', 'storm', 'montblanc', 'lemon', 'chooyu', 'raspberry', 'cherry', 'thunder', 'vinson', 'breeze', 'elbrus', 'everest', 'parbat', 'makalu', 'nanga', 'kangchenjunga', 'lightning', 'cyclone', 'comet', 'asteroid', 'pomegranate', 'nectarine', 'clementine', 'strawberry', 'tornado', 'avalanche', 'andes', 'rockies', 'himalayas', 'pyrenees', 'carpathians', 'cascade', 'etna', 'vesuvius', 'volcano', 'tundra', 'whirlwind', 'iceberg', 'eclipse', 'zephyr', 'tropic', 'monsoon', 'aurora'];
|
|
1052
|
-
return Array.from(
|
|
1053
|
-
{ length: 4 },
|
|
1054
|
-
() => dictionary[Math.floor(Math.random() * dictionary.length)]
|
|
1055
|
-
).join("-");
|
|
1027
|
+
return Array.from({ length: 3 },() => dictionary[Math.floor(Math.random() * dictionary.length)]).join("-");
|
|
1056
1028
|
}
|
|
1057
1029
|
}
|
|
1058
1030
|
|
|
@@ -1080,8 +1052,13 @@ export class ValidationError extends Error {
|
|
|
1080
1052
|
constructor(errors = []) {
|
|
1081
1053
|
// Create a message based on the list of errors
|
|
1082
1054
|
//console.log(errors)
|
|
1083
|
-
let error_messages
|
|
1084
|
-
|
|
1055
|
+
let error_messages
|
|
1056
|
+
if(Array.isArray(errors)){
|
|
1057
|
+
error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
|
|
1058
|
+
}else {
|
|
1059
|
+
error_messages = [errors]
|
|
1060
|
+
}
|
|
1061
|
+
let message = `Validation failed with error(s): ${error_messages.join(",")}`;
|
|
1085
1062
|
super(message);
|
|
1086
1063
|
this.name = 'ValidationError';
|
|
1087
1064
|
this.errors = errors; // Store the list of errors
|
|
@@ -1103,7 +1080,12 @@ export class DocUpdateError extends Error {
|
|
|
1103
1080
|
* @param {ErrorItem[]} [errors=[]] - An array of error objects, each containing details about validation failures.
|
|
1104
1081
|
*/
|
|
1105
1082
|
constructor(errors=[]){
|
|
1106
|
-
let error_messages
|
|
1083
|
+
let error_messages
|
|
1084
|
+
if(Array.isArray(errors)){
|
|
1085
|
+
error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
|
|
1086
|
+
}else {
|
|
1087
|
+
error_messages = [errors]
|
|
1088
|
+
}
|
|
1107
1089
|
let message = `Error in document update. ${error_messages.join(",")}`
|
|
1108
1090
|
super(message)
|
|
1109
1091
|
this.name = "DocUpdateError";
|
|
@@ -1116,7 +1098,7 @@ export class DocUpdateError extends Error {
|
|
|
1116
1098
|
*
|
|
1117
1099
|
* @extends {Error}
|
|
1118
1100
|
*/
|
|
1119
|
-
export class
|
|
1101
|
+
export class DocCreationError extends Error {
|
|
1120
1102
|
/**
|
|
1121
1103
|
* Custom error class for document insert errors.
|
|
1122
1104
|
*
|
|
@@ -1124,10 +1106,15 @@ export class DocInsertError extends Error {
|
|
|
1124
1106
|
* @param {ErrorItem[]} [errors=[]] - An array of error objects, each containing details about validation failures.
|
|
1125
1107
|
*/
|
|
1126
1108
|
constructor(errors=[]){
|
|
1127
|
-
let error_messages
|
|
1128
|
-
|
|
1109
|
+
let error_messages
|
|
1110
|
+
if(Array.isArray(errors)){
|
|
1111
|
+
error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
|
|
1112
|
+
}else {
|
|
1113
|
+
error_messages = [errors]
|
|
1114
|
+
}
|
|
1115
|
+
let message = `Error in document creation. ${error_messages.join(",")}`
|
|
1129
1116
|
super(message)
|
|
1130
|
-
this.name = "
|
|
1117
|
+
this.name = "DocCreationError";
|
|
1131
1118
|
this.errors = errors
|
|
1132
1119
|
}
|
|
1133
1120
|
}
|
|
@@ -1145,10 +1132,42 @@ export class DocNotFoundError extends Error {
|
|
|
1145
1132
|
* @param {ErrorItem[]} [errors=[]] - An array of error objects, each containing details about validation failures.
|
|
1146
1133
|
*/
|
|
1147
1134
|
constructor(errors=[]){
|
|
1148
|
-
let error_messages
|
|
1135
|
+
let error_messages
|
|
1136
|
+
if(Array.isArray(errors)){
|
|
1137
|
+
error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
|
|
1138
|
+
}else {
|
|
1139
|
+
error_messages = [errors]
|
|
1140
|
+
}
|
|
1149
1141
|
let message = `Error in fetching document. Criteria : ${error_messages.join(",")}`
|
|
1150
1142
|
super(message)
|
|
1151
1143
|
this.name = "DocNotFoundError";
|
|
1152
1144
|
this.errors = errors
|
|
1153
1145
|
}
|
|
1154
1146
|
}
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Custom error class for encryption error.
|
|
1151
|
+
*
|
|
1152
|
+
* @extends {Error}
|
|
1153
|
+
*/
|
|
1154
|
+
export class EncryptionError extends Error {
|
|
1155
|
+
/**
|
|
1156
|
+
* Custom error class for document not found errors.
|
|
1157
|
+
*
|
|
1158
|
+
* @extends {Error}
|
|
1159
|
+
* @param {ErrorItem[]} [errors=[]] - An array of error objects, each containing details about validation failures.
|
|
1160
|
+
*/
|
|
1161
|
+
constructor(errors=[]){
|
|
1162
|
+
let error_messages
|
|
1163
|
+
if(Array.isArray(errors)){
|
|
1164
|
+
error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
|
|
1165
|
+
}else {
|
|
1166
|
+
error_messages = [errors]
|
|
1167
|
+
}
|
|
1168
|
+
let message = `Error in encryption/decryption of data : ${error_messages.join(",")}`
|
|
1169
|
+
super(message)
|
|
1170
|
+
this.name = "EncryptionError";
|
|
1171
|
+
this.errors = errors
|
|
1172
|
+
}
|
|
1173
|
+
}
|