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/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","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 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
+ // 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
- 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
- );
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
- doc_obj = this._decrypt_doc(s_doc, doc_obj);
604
- return doc_obj;
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
- * @private
631
- * @returns {number}
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. Note that no validation is done. This is for internal use
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
- 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
- });
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
- return { ...doc_obj };
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
- 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
- });
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
- selector: { schema: "schema", "data.name": schema },
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
- 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
- }
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 Error("Doc already exists");
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
- 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
- });
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"]["additionalProperties"]) {
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 four randomly
1019
+ * @returns {String} A hyphen-separated string containing three randomly
1045
1020
  * selected words from the dictionary. For example:
1046
- * "banana-earth-kiwi-rain".
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 = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
1084
- let message = `Validation failed with ${errors.length} error(s): ${error_messages.join(",")}`;
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 = errors.map(item=>`${item.message}`)
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 DocInsertError extends Error {
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 = errors.map(item=>`${item.message}`)
1128
- let message = `Error in document insert. ${error_messages.join(",")}`
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 = "DocInsertError";
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 = errors.map(item=>`${item.message}`)
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
+ }