beanbagdb 0.5.52 → 0.5.53
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/package.json +3 -2
- package/src/index.js +259 -265
- package/src/system_schema.js +34 -4
- package/test/couchdb.js +2 -2
- package/test/operations.test.js +1687 -286
- package/test/pouchdb.js +12 -6
- package/test/test1.js +134 -168
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",""]
|
|
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,61 @@ 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
|
-
let schema_doc = await this.get_schema(schema_name);
|
|
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
|
+
schema:async (criteria)=>{
|
|
601
|
+
let schemaSearch = await this.db_api.search({
|
|
602
|
+
selector: { schema: "schema", "data.name": criteria.name },
|
|
603
|
+
});
|
|
604
|
+
// console.log(schemaSearch)
|
|
605
|
+
if (schemaSearch.docs.length == 0) {
|
|
606
|
+
throw new DocNotFoundError(BeanBagDB.error_codes.schema_not_found);
|
|
589
607
|
}
|
|
590
|
-
|
|
591
|
-
});
|
|
592
|
-
let s = await this.search({ selector: search_criteria });
|
|
593
|
-
doc_obj = s.docs[0];
|
|
594
|
-
} else {
|
|
595
|
-
let s = await this.search({ selector: { schema: schema_name } });
|
|
596
|
-
if (s.docs.length > 1) {
|
|
597
|
-
throw new Error(
|
|
598
|
-
"Invalid schema. At least one primary key must be defined or set the singleRecord option to true. "
|
|
599
|
-
);
|
|
608
|
+
return schemaSearch.docs[0];
|
|
600
609
|
}
|
|
601
|
-
doc_obj = s.docs[0];
|
|
602
610
|
}
|
|
603
|
-
|
|
604
|
-
|
|
611
|
+
if(Object.keys(fetch_docs).includes(special_doc_type)){
|
|
612
|
+
let data = await fetch_docs[special_doc_type](criteria)
|
|
613
|
+
return data
|
|
614
|
+
}else{
|
|
615
|
+
throw new ValidationError("Invalid special doc type. Must be : "+Object.keys(fetch_docs).join(","))
|
|
616
|
+
}
|
|
605
617
|
}
|
|
606
618
|
|
|
607
|
-
|
|
608
619
|
async load_plugin(plugin_name, plugin_module) {
|
|
609
620
|
this._check_ready_to_use();
|
|
610
621
|
this.plugins[plugin_name] = {};
|
|
611
622
|
for (let func_name in plugin_module) {
|
|
612
623
|
if (typeof plugin_module[func_name] == "function") {
|
|
613
|
-
this.plugins[plugin_name][func_name] = plugin_module[func_name].bind(
|
|
614
|
-
null,
|
|
615
|
-
this
|
|
616
|
-
);
|
|
624
|
+
this.plugins[plugin_name][func_name] = plugin_module[func_name].bind(null,this)
|
|
617
625
|
}
|
|
618
626
|
}
|
|
619
627
|
// Check if the plugin has an on_load method and call it
|
|
@@ -626,10 +634,15 @@ export class BeanBagDB {
|
|
|
626
634
|
//////////////// Internal methods ////////////////////////
|
|
627
635
|
//////////////////////////////////////////////////////////
|
|
628
636
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
637
|
+
/**
|
|
638
|
+
* Retrieves the current version of the system by summing up the version numbers
|
|
639
|
+
* of all system-defined schemas.
|
|
640
|
+
*
|
|
641
|
+
* @private
|
|
642
|
+
* @returns {number} The total sum of the version numbers of all system-defined schemas.
|
|
643
|
+
*
|
|
644
|
+
* @throws {Error} Throws if there is an issue calculating the sum of version numbers.
|
|
645
|
+
*/
|
|
633
646
|
_get_current_version() {
|
|
634
647
|
// current version is the sum of versions of all system defined schemas
|
|
635
648
|
let sum = sys_sch.schema_schema.version;
|
|
@@ -681,7 +694,7 @@ export class BeanBagDB {
|
|
|
681
694
|
}
|
|
682
695
|
|
|
683
696
|
/**
|
|
684
|
-
* Generates a blank schema doc ready to be inserted to the database.
|
|
697
|
+
* Generates a blank schema doc ready to be inserted to the database. This is for internal use
|
|
685
698
|
* @private
|
|
686
699
|
* @param {string} schema_name
|
|
687
700
|
* @param {Object} schema_object
|
|
@@ -702,19 +715,17 @@ export class BeanBagDB {
|
|
|
702
715
|
* @param {Object} doc_obj
|
|
703
716
|
* @returns {Object}
|
|
704
717
|
*/
|
|
705
|
-
_decrypt_doc(schema_obj, doc_obj) {
|
|
706
|
-
|
|
707
|
-
schema_obj.settings["encrypted_fields"]
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
);
|
|
715
|
-
});
|
|
718
|
+
async _decrypt_doc(schema_obj, doc_obj) {
|
|
719
|
+
try {
|
|
720
|
+
for (let itm of schema_obj.settings["encrypted_fields"]) {
|
|
721
|
+
doc_obj.data[itm] = await this.utils.decrypt(doc_obj.data[itm], this.encryption_key);
|
|
722
|
+
}
|
|
723
|
+
return { ...doc_obj };
|
|
724
|
+
} catch (error) {
|
|
725
|
+
console.log(error)
|
|
726
|
+
throw new EncryptionError([{message:error.message}])
|
|
716
727
|
}
|
|
717
|
-
|
|
728
|
+
|
|
718
729
|
}
|
|
719
730
|
|
|
720
731
|
/**
|
|
@@ -724,20 +735,18 @@ export class BeanBagDB {
|
|
|
724
735
|
* @param {Object} doc_obj
|
|
725
736
|
* @returns {Object}
|
|
726
737
|
*/
|
|
727
|
-
_encrypt_doc(schema_obj, doc_obj) {
|
|
728
|
-
|
|
729
|
-
schema_obj.settings["encrypted_fields"]
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
});
|
|
738
|
+
async _encrypt_doc(schema_obj, doc_obj) {
|
|
739
|
+
try {
|
|
740
|
+
if (schema_obj.settings["encrypted_fields"].length > 0) {
|
|
741
|
+
// console.log(schema_obj,doc_obj)
|
|
742
|
+
for (let itm of schema_obj.settings["encrypted_fields"]) {
|
|
743
|
+
doc_obj.data[itm] = await this.utils.encrypt(doc_obj.data[itm], this.encryption_key);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return { ...doc_obj };
|
|
747
|
+
} catch (error) {
|
|
748
|
+
throw new EncryptionError([{message:error.message}])
|
|
739
749
|
}
|
|
740
|
-
return { ...doc_obj };
|
|
741
750
|
}
|
|
742
751
|
|
|
743
752
|
/**
|
|
@@ -762,12 +771,8 @@ export class BeanBagDB {
|
|
|
762
771
|
*/
|
|
763
772
|
async _insert_pre_checks(schema, data, meta = {}, settings = {}) {
|
|
764
773
|
// 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
|
-
}
|
|
774
|
+
let sch_search = await this.search({selector: { schema: "schema", "data.name": schema }})
|
|
775
|
+
if (sch_search.docs.length == 0) {throw new DocCreationError(`The schema "${schema}" does not exists`)}
|
|
771
776
|
let schemaDoc = sch_search.docs[0]["data"];
|
|
772
777
|
// validate data
|
|
773
778
|
this.util_validate_data(schemaDoc.schema, data);
|
|
@@ -777,13 +782,8 @@ export class BeanBagDB {
|
|
|
777
782
|
|
|
778
783
|
// duplicate meta.link check
|
|
779
784
|
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
|
-
}
|
|
785
|
+
let link_search = await this.search({selector: { "meta.link": meta.link }})
|
|
786
|
+
if (link_search.docs.length > 0) {throw new DocCreationError(`Document with the link "${meta.link}" already exists in the Database.`)}
|
|
787
787
|
}
|
|
788
788
|
|
|
789
789
|
// special checks for special docs
|
|
@@ -793,36 +793,32 @@ export class BeanBagDB {
|
|
|
793
793
|
this.util_validate_schema_object(data);
|
|
794
794
|
}
|
|
795
795
|
// @TODO : check if single record setting is set to true
|
|
796
|
-
|
|
796
|
+
//console.log(schemaDoc)
|
|
797
797
|
// duplicate check
|
|
798
|
-
if (
|
|
799
|
-
schemaDoc.settings["primary_keys"] &&
|
|
800
|
-
schemaDoc.settings["primary_keys"].length > 0
|
|
801
|
-
) {
|
|
798
|
+
if (schemaDoc.settings["primary_keys"].length > 0) {
|
|
802
799
|
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);
|
|
800
|
+
schemaDoc.settings["primary_keys"].map((ky) => {primary_obj["data." + ky] = data[ky];});
|
|
807
801
|
let prim_search = await this.search({ selector: primary_obj });
|
|
808
|
-
console.log(prim_search);
|
|
809
802
|
if (prim_search.docs.length > 0) {
|
|
810
|
-
throw new
|
|
803
|
+
throw new DocCreationError(`Document with the given primary key (${schemaDoc.settings["primary_keys"].join(",")}) already exists in the schema "${schema}"`);
|
|
811
804
|
}
|
|
812
805
|
}
|
|
813
806
|
// encrypt if required
|
|
814
807
|
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
|
-
});
|
|
808
|
+
if (schemaDoc.settings["encrypted_fields"].length > 0) {
|
|
809
|
+
// todo test if encryption is successful
|
|
810
|
+
for (let itm of schemaDoc.settings["encrypted_fields"]) {
|
|
811
|
+
new_data[itm] = await this.utils.encrypt(data[itm], this.encryption_key);
|
|
812
|
+
}
|
|
822
813
|
}
|
|
814
|
+
|
|
823
815
|
// generate the doc object for data
|
|
824
816
|
let doc_obj = this._get_blank_doc(schema);
|
|
825
817
|
doc_obj["data"] = new_data;
|
|
818
|
+
// if meta exists
|
|
819
|
+
if(meta){
|
|
820
|
+
doc_obj["meta"] = {...doc_obj["meta"],...meta};
|
|
821
|
+
}
|
|
826
822
|
return doc_obj;
|
|
827
823
|
}
|
|
828
824
|
|
|
@@ -830,16 +826,13 @@ export class BeanBagDB {
|
|
|
830
826
|
////// Utility methods /////////////////////////////////////////////////////////////////////////////
|
|
831
827
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
832
828
|
|
|
833
|
-
|
|
834
829
|
/**
|
|
835
830
|
* Returns the current Unix timestamp in seconds.
|
|
836
831
|
* divide by 1000 (Date.now gives ms) to convert to seconds. 1 s = 1000 ms
|
|
837
832
|
* @private
|
|
838
833
|
* @returns {number}
|
|
839
834
|
*/
|
|
840
|
-
util_get_now_unix_timestamp() {
|
|
841
|
-
return Math.floor(Date.now() / 1000);
|
|
842
|
-
}
|
|
835
|
+
util_get_now_unix_timestamp() {return Math.floor(Date.now() / 1000)}
|
|
843
836
|
|
|
844
837
|
/**
|
|
845
838
|
* Validates that the required fields are present in the provided object.
|
|
@@ -850,11 +843,7 @@ export class BeanBagDB {
|
|
|
850
843
|
*/
|
|
851
844
|
util_check_required_fields(requiredFields, obj) {
|
|
852
845
|
for (const field of requiredFields) {
|
|
853
|
-
if (!obj[field]) {
|
|
854
|
-
throw new ValidationError([
|
|
855
|
-
{ message: `The field ${field} is required.` },
|
|
856
|
-
]);
|
|
857
|
-
}
|
|
846
|
+
if (!obj[field]) {throw new ValidationError(`The field ${field} is required.`)}
|
|
858
847
|
}
|
|
859
848
|
}
|
|
860
849
|
|
|
@@ -890,13 +879,7 @@ export class BeanBagDB {
|
|
|
890
879
|
* @throws {Error} If the data object does not conform to the schema
|
|
891
880
|
*/
|
|
892
881
|
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);
|
|
882
|
+
const { valid, validate } = this.utils.validate_schema(schema_obj,data_obj)
|
|
900
883
|
if (!valid) {
|
|
901
884
|
throw new ValidationError(validate.errors);
|
|
902
885
|
}
|
|
@@ -932,15 +915,10 @@ export class BeanBagDB {
|
|
|
932
915
|
util_validate_schema_object(schema_doc) {
|
|
933
916
|
let errors = [{ message: "Schema validation errors " }];
|
|
934
917
|
if (!schema_doc["schema"]["type"]) {
|
|
935
|
-
errors.push({
|
|
936
|
-
message:
|
|
937
|
-
"Schema must have the field schema.'type' which can only be 'object' ",
|
|
938
|
-
});
|
|
918
|
+
errors.push({message:"Schema must have the field schema.'type' which can only be 'object' "});
|
|
939
919
|
} else {
|
|
940
920
|
if (schema_doc["schema"]["type"] != "object") {
|
|
941
|
-
errors.push({
|
|
942
|
-
message: "The schema.'type' value is invalid.Only 'object' allowed",
|
|
943
|
-
});
|
|
921
|
+
errors.push({message: "The schema.'type' value is invalid.Only 'object' allowed",});
|
|
944
922
|
}
|
|
945
923
|
}
|
|
946
924
|
if (!schema_doc["schema"]["properties"]) {
|
|
@@ -949,26 +927,18 @@ export class BeanBagDB {
|
|
|
949
927
|
});
|
|
950
928
|
} else {
|
|
951
929
|
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
|
-
});
|
|
930
|
+
errors.push({message:"Invalid schema.properties. It must be an object and must have atleast one field inside.",});
|
|
956
931
|
}
|
|
957
932
|
if (Object.keys(schema_doc["schema"]["properties"]).length == 0) {
|
|
958
933
|
errors.push({ message: "You must define at least one property" });
|
|
959
934
|
}
|
|
960
935
|
}
|
|
961
936
|
|
|
962
|
-
if (!schema_doc["schema"]
|
|
963
|
-
errors.push({
|
|
964
|
-
message: "The schema.'additionalProperties' field is required",
|
|
965
|
-
});
|
|
937
|
+
if (!schema_doc["schema"].hasOwnProperty("additionalProperties")) {
|
|
938
|
+
errors.push({message: "The schema.'additionalProperties' field is required",});
|
|
966
939
|
} else {
|
|
967
940
|
if (typeof schema_doc["schema"]["additionalProperties"] != "boolean") {
|
|
968
|
-
errors.push({
|
|
969
|
-
message:
|
|
970
|
-
"Invalid schema.additionalProperties. It must be a boolean value",
|
|
971
|
-
});
|
|
941
|
+
errors.push({message:"Invalid schema.additionalProperties. It must be a boolean value",});
|
|
972
942
|
}
|
|
973
943
|
}
|
|
974
944
|
|
|
@@ -983,10 +953,7 @@ export class BeanBagDB {
|
|
|
983
953
|
);
|
|
984
954
|
|
|
985
955
|
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
|
-
});
|
|
956
|
+
errors.push({message:"Primary keys invalid. All keys must be defined in the schema and must be non object",});
|
|
990
957
|
}
|
|
991
958
|
}
|
|
992
959
|
|
|
@@ -996,63 +963,43 @@ export class BeanBagDB {
|
|
|
996
963
|
(item) => allKeys.includes(item)
|
|
997
964
|
);
|
|
998
965
|
if (!all_ne_exist) {
|
|
999
|
-
errors.push({
|
|
1000
|
-
message:
|
|
1001
|
-
"Non editable fields invalid. All fields must be defined in the schema ",
|
|
1002
|
-
});
|
|
966
|
+
errors.push({message:"Non editable fields invalid. All fields must be defined in the schema ",});
|
|
1003
967
|
}
|
|
1004
968
|
}
|
|
1005
969
|
|
|
1006
970
|
if (schema_doc["settings"]["encrypted_fields"].length > 0) {
|
|
1007
971
|
// 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
|
-
);
|
|
972
|
+
let all_enc_exist = schema_doc["settings"]["encrypted_fields"].every((item) =>allKeys.includes(item) &&schema_doc["schema"]["properties"][item]["type"] == "string");
|
|
1013
973
|
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
|
-
});
|
|
974
|
+
errors.push({message:"Invalid encrypted fields. All fields must be defined in the schema and must be string ",});
|
|
1018
975
|
}
|
|
1019
976
|
|
|
1020
977
|
// 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
|
-
);
|
|
978
|
+
let all_enc_no_pk = schema_doc["settings"]["encrypted_fields"].every((item) => !schema_doc["settings"]["primary_keys"].includes(item));
|
|
1024
979
|
if (!all_enc_no_pk) {
|
|
1025
|
-
errors.push({
|
|
1026
|
-
message:
|
|
1027
|
-
"Invalid encrypted fields.Primary key fields cannot be encrypted ",
|
|
1028
|
-
});
|
|
980
|
+
errors.push({message:"Invalid encrypted fields.Primary key fields cannot be encrypted ",});
|
|
1029
981
|
}
|
|
1030
982
|
}
|
|
1031
983
|
|
|
1032
984
|
/// cannot encrypt primary field keys
|
|
1033
|
-
if (errors.length > 1) {
|
|
1034
|
-
throw new ValidationError(errors);
|
|
1035
|
-
}
|
|
985
|
+
if (errors.length > 1) {throw new ValidationError(errors)}
|
|
1036
986
|
}
|
|
1037
987
|
|
|
1038
|
-
|
|
988
|
+
/**
|
|
1039
989
|
* Generates a random link composed of four words from a predefined dictionary.
|
|
1040
990
|
*
|
|
1041
991
|
* The words are selected randomly, and the resulting link is formatted as
|
|
1042
992
|
* a hyphen-separated string. This can be useful for creating link for documents.
|
|
1043
993
|
*
|
|
1044
|
-
* @returns {String} A hyphen-separated string containing
|
|
994
|
+
* @returns {String} A hyphen-separated string containing three randomly
|
|
1045
995
|
* selected words from the dictionary. For example:
|
|
1046
|
-
* "banana-earth-
|
|
996
|
+
* "banana-earth-rain".
|
|
1047
997
|
*
|
|
1048
998
|
*/
|
|
1049
999
|
util_generate_random_link() {
|
|
1050
1000
|
// prettier-ignore
|
|
1051
1001
|
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("-");
|
|
1002
|
+
return Array.from({ length: 3 },() => dictionary[Math.floor(Math.random() * dictionary.length)]).join("-");
|
|
1056
1003
|
}
|
|
1057
1004
|
}
|
|
1058
1005
|
|
|
@@ -1080,8 +1027,13 @@ export class ValidationError extends Error {
|
|
|
1080
1027
|
constructor(errors = []) {
|
|
1081
1028
|
// Create a message based on the list of errors
|
|
1082
1029
|
//console.log(errors)
|
|
1083
|
-
let error_messages
|
|
1084
|
-
|
|
1030
|
+
let error_messages
|
|
1031
|
+
if(Array.isArray(errors)){
|
|
1032
|
+
error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
|
|
1033
|
+
}else {
|
|
1034
|
+
error_messages = [errors]
|
|
1035
|
+
}
|
|
1036
|
+
let message = `Validation failed with error(s): ${error_messages.join(",")}`;
|
|
1085
1037
|
super(message);
|
|
1086
1038
|
this.name = 'ValidationError';
|
|
1087
1039
|
this.errors = errors; // Store the list of errors
|
|
@@ -1103,7 +1055,12 @@ export class DocUpdateError extends Error {
|
|
|
1103
1055
|
* @param {ErrorItem[]} [errors=[]] - An array of error objects, each containing details about validation failures.
|
|
1104
1056
|
*/
|
|
1105
1057
|
constructor(errors=[]){
|
|
1106
|
-
let error_messages
|
|
1058
|
+
let error_messages
|
|
1059
|
+
if(Array.isArray(errors)){
|
|
1060
|
+
error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
|
|
1061
|
+
}else {
|
|
1062
|
+
error_messages = [errors]
|
|
1063
|
+
}
|
|
1107
1064
|
let message = `Error in document update. ${error_messages.join(",")}`
|
|
1108
1065
|
super(message)
|
|
1109
1066
|
this.name = "DocUpdateError";
|
|
@@ -1116,7 +1073,7 @@ export class DocUpdateError extends Error {
|
|
|
1116
1073
|
*
|
|
1117
1074
|
* @extends {Error}
|
|
1118
1075
|
*/
|
|
1119
|
-
export class
|
|
1076
|
+
export class DocCreationError extends Error {
|
|
1120
1077
|
/**
|
|
1121
1078
|
* Custom error class for document insert errors.
|
|
1122
1079
|
*
|
|
@@ -1124,10 +1081,15 @@ export class DocInsertError extends Error {
|
|
|
1124
1081
|
* @param {ErrorItem[]} [errors=[]] - An array of error objects, each containing details about validation failures.
|
|
1125
1082
|
*/
|
|
1126
1083
|
constructor(errors=[]){
|
|
1127
|
-
let error_messages
|
|
1128
|
-
|
|
1084
|
+
let error_messages
|
|
1085
|
+
if(Array.isArray(errors)){
|
|
1086
|
+
error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
|
|
1087
|
+
}else {
|
|
1088
|
+
error_messages = [errors]
|
|
1089
|
+
}
|
|
1090
|
+
let message = `Error in document creation. ${error_messages.join(",")}`
|
|
1129
1091
|
super(message)
|
|
1130
|
-
this.name = "
|
|
1092
|
+
this.name = "DocCreationError";
|
|
1131
1093
|
this.errors = errors
|
|
1132
1094
|
}
|
|
1133
1095
|
}
|
|
@@ -1145,10 +1107,42 @@ export class DocNotFoundError extends Error {
|
|
|
1145
1107
|
* @param {ErrorItem[]} [errors=[]] - An array of error objects, each containing details about validation failures.
|
|
1146
1108
|
*/
|
|
1147
1109
|
constructor(errors=[]){
|
|
1148
|
-
let error_messages
|
|
1110
|
+
let error_messages
|
|
1111
|
+
if(Array.isArray(errors)){
|
|
1112
|
+
error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
|
|
1113
|
+
}else {
|
|
1114
|
+
error_messages = [errors]
|
|
1115
|
+
}
|
|
1149
1116
|
let message = `Error in fetching document. Criteria : ${error_messages.join(",")}`
|
|
1150
1117
|
super(message)
|
|
1151
1118
|
this.name = "DocNotFoundError";
|
|
1152
1119
|
this.errors = errors
|
|
1153
1120
|
}
|
|
1154
1121
|
}
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* Custom error class for encryption error.
|
|
1126
|
+
*
|
|
1127
|
+
* @extends {Error}
|
|
1128
|
+
*/
|
|
1129
|
+
export class EncryptionError extends Error {
|
|
1130
|
+
/**
|
|
1131
|
+
* Custom error class for document not found errors.
|
|
1132
|
+
*
|
|
1133
|
+
* @extends {Error}
|
|
1134
|
+
* @param {ErrorItem[]} [errors=[]] - An array of error objects, each containing details about validation failures.
|
|
1135
|
+
*/
|
|
1136
|
+
constructor(errors=[]){
|
|
1137
|
+
let error_messages
|
|
1138
|
+
if(Array.isArray(errors)){
|
|
1139
|
+
error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
|
|
1140
|
+
}else {
|
|
1141
|
+
error_messages = [errors]
|
|
1142
|
+
}
|
|
1143
|
+
let message = `Error in encryption/decryption of data : ${error_messages.join(",")}`
|
|
1144
|
+
super(message)
|
|
1145
|
+
this.name = "EncryptionError";
|
|
1146
|
+
this.errors = errors
|
|
1147
|
+
}
|
|
1148
|
+
}
|