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/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.get_schema("schema");
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.get_schema(schema_name);
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 { id: new_rec["id"] };
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: none };
382
+ this._check_ready_to_use()
383
+ let obj = { doc: null }
384
+ let data_schema = null
384
385
  if (criteria._id) {
385
- let doc = await this.db_api.get(criteria._id);
386
- obj.doc = doc;
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
- let pkSearch = await this.db_api.search({selector: { "schema": criteria.schema, "data":criteria.data },});
393
- if (pkSearch.docs.length == 0) {throw new DocNotFoundError(BeanBagDB.error_codes.doc_not_found);}
394
- obj.doc = pkSearch.docs[0];
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
- if (include_schema) {
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 allowed_updates = this.util_filter_object(updates.data, edit_fields);
473
- let updated_data = { ...full_doc.data, ...allowed_updates };
474
-
475
- this.util_validate_data(schema.schema, updated_data);
476
-
477
- // primary key check if multiple records can be created
478
- if ( schema.settings["single_record"] == false && schema.settings["primary_keys"].length > 0) {
479
- let pri_fields = schema.settings["primary_keys"];
480
- let search_criteria = { schema: schema.name };
481
- pri_fields.map((itm) => {search_criteria["data." + itm] = updated_data[itm];});
482
- let search = await this.search({ selection: search_criteria });
483
- if (search.docs.length > 0) {
484
- if (search.docs.length == 1) {
485
- let thedoc = search.docs[0];
486
- if (thedoc["_id"] != doc_id) {
487
- throw new DocUpdateError([{message:"Update not allowed. Document with the same primary key already exists",}]);
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(doc_id) {
548
+ async delete(criteria) {
522
549
  this._check_ready_to_use();
523
- await this.db_api.delete(doc_id);
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 Error("Invalid search query.");
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
- * Returns schema document for the given schema name
552
- * @param {String} schema_name - Schema name
553
- */
554
- async get_schema(schema_name) {
555
- let schemaSearch = await this.db_api.search({
556
- selector: { schema: "schema", "data.name": schema_name },
557
- });
558
- // console.log(schemaSearch)
559
- if (schemaSearch.docs.length == 0) {
560
- throw new DocNotFoundError([{message:BeanBagDB.error_codes.schema_not_found}]);
561
- }
562
- return schemaSearch.docs[0];
563
- }
564
-
565
- /**
566
- * Fetches a document based on a given schema and primary key.
567
- * In case schema has a single record, leave the primary_key blank `[]`
568
- * Can also be used to get special system docs such as settings
569
- * @param {String} schema_name
570
- * @param {Object} primary_key
571
- * @returns object
572
- */
573
- async get_doc(schema_name, primary_key = {}) {
574
- this._check_ready_to_use();
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
- search_criteria["data." + itm] = primary_key[itm];
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
- doc_obj = this._decrypt_doc(s_doc, doc_obj);
604
- return doc_obj;
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
- * @private
631
- * @returns {number}
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. Note that no validation is done. This is for internal use
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
- if (
707
- schema_obj.settings["encrypted_fields"] &&
708
- schema_obj.settings["encrypted_fields"].length > 0
709
- ) {
710
- schema_obj.settings["encrypted_fields"].forEach((itm) => {
711
- doc_obj.data[itm] = this.utils.decrypt(
712
- doc_obj.data[itm],
713
- this.encryption_key
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
- return { ...doc_obj };
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
- if (
729
- schema_obj.settings["encrypted_fields"] &&
730
- schema_obj.settings["encrypted_fields"].length > 0
731
- ) {
732
- // console.log(schema_obj,doc_obj)
733
- schema_obj.settings["encrypted_fields"].forEach((itm) => {
734
- doc_obj.data[itm] = this.utils.encrypt(
735
- doc_obj.data[itm],
736
- this.encryption_key
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
- selector: { schema: "schema", "data.name": schema },
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
- selector: { "meta.link": meta.link },
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 Error("Doc already exists");
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
- schemaDoc.settings["encrypted_fields"] &&
817
- schemaDoc.settings["encrypted_fields"].length > 0
818
- ) {
819
- schemaDoc.settings["encrypted_fields"].forEach((itm) => {
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"]["additionalProperties"]) {
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 four randomly
994
+ * @returns {String} A hyphen-separated string containing three randomly
1045
995
  * selected words from the dictionary. For example:
1046
- * "banana-earth-kiwi-rain".
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 = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
1084
- let message = `Validation failed with ${errors.length} error(s): ${error_messages.join(",")}`;
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 = errors.map(item=>`${item.message}`)
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 DocInsertError extends Error {
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 = errors.map(item=>`${item.message}`)
1128
- let message = `Error in document insert. ${error_messages.join(",")}`
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 = "DocInsertError";
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 = errors.map(item=>`${item.message}`)
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
+ }