beanbagdb 0.8.2 → 0.8.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beanbagdb",
3
- "version": "0.8.2",
3
+ "version": "0.8.5",
4
4
  "description": "A JS library to introduce a schema layer to a No-SQL local database",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
package/src/index.js CHANGED
@@ -39,11 +39,12 @@ export class BeanBagDB {
39
39
  * @param {function} db_instance.utils.decrypt - Decrypts a document.
40
40
  * @param {function} db_instance.utils.ping - Checks the database connection.
41
41
  * @param {function} db_instance.utils.validate_schema - Validates the database schema.
42
+ * @param {function} db_instance.utils.compile_template - Compiles a text template with data. {}
42
43
  */
43
44
  constructor(db_instance) {
44
45
  this.util_check_required_fields(["name", "encryption_key", "api", "utils", "db_name"],db_instance)
45
46
  this.util_check_required_fields(["insert", "update", "delete", "search", "get", "createIndex"],db_instance.api)
46
- this.util_check_required_fields(["encrypt", "decrypt", "ping", "validate_schema"],db_instance.utils)
47
+ this.util_check_required_fields(["encrypt", "decrypt", "ping", "validate_schema","compile_template"],db_instance.utils)
47
48
 
48
49
  if (db_instance.encryption_key.length < 20) {
49
50
  throw new ValidationError([{ message: BeanBagDB.error_codes.key_short }]);
@@ -445,6 +446,7 @@ export class BeanBagDB {
445
446
  * @param {string} [criteria.schema] - The schema name used when searching by primary keys.
446
447
  * @param {Object} [criteria.data] - Data object containing the schema's primary keys for search.
447
448
  * @param {string} [criteria.include_schema] - Whether to include the schema object in the returned result.
449
+ * @param {string} [criteria.text_template] - The name of the text template. If provided, an additional field called 'view' is returned with the document in the specified text format.
448
450
  *
449
451
  * @returns {Promise<Object>} - Returns an object with the document (`doc`) and optionally the schema (`schema`).
450
452
  *
@@ -491,6 +493,12 @@ export class BeanBagDB {
491
493
  // decrypt the document
492
494
  obj.doc = await this._decrypt_doc(data_schema["data"], obj.doc)
493
495
 
496
+ if(criteria.text_template){
497
+ let doc_view = this._compile_template(criteria.text_template,data_schema["data"],obj.doc)
498
+ obj["view"] = doc_view
499
+ }
500
+
501
+
494
502
  return obj;
495
503
  }
496
504
 
@@ -721,10 +729,38 @@ export class BeanBagDB {
721
729
  //if (!criteria["selector"]["schema"]) {
722
730
  // throw new Error("The search criteria must contain the schema");
723
731
  //}
724
- const results = await this.db_api.search(criteria);
732
+ let results = await this.db_api.search(criteria);
733
+ let def_options = {decrypt_docs:false}
734
+ let options = { ...def_options, ...criteria?.options||{}, }
735
+ // console.log(options)
736
+ if(options["decrypt_docs"]){
737
+ if(results.docs.length>0){
738
+ results = await this._decrypt_docs(results)
739
+ }
740
+ }
741
+
725
742
  return results;
726
743
  }
727
744
 
745
+ async _decrypt_docs(search_results){
746
+ const uniqueSchemas = [...new Set(search_results.docs.map(item => item.schema))];
747
+ let schemaSearch = await this.db_api.search({
748
+ selector: { schema: "schema", "data.name":{ "$in":uniqueSchemas } },
749
+ });
750
+ //console.log(schemaSearch)
751
+ let schema = {}
752
+ schemaSearch.docs.map(itm=>{
753
+ schema[itm.data.name] = itm.data
754
+ })
755
+ //console.log(schema)
756
+ let d_results = []
757
+ for (let index = 0; index < search_results.docs.length; index++) {
758
+ const element = search_results.docs[index];
759
+ d_results.push(await this._decrypt_doc(schema[element.schema],element))
760
+ }
761
+ return {docs:d_results}
762
+ }
763
+
728
764
  /**
729
765
  * Retrieves special types of documents from the database, such as schema documents or blank documents
730
766
  * for a given schema. It handles system-related data and throws errors for invalid document types
@@ -1017,6 +1053,80 @@ _check_nodes_edge(node1Rule, node2Rule, schema1, schema2) {
1017
1053
  //////////////// Internal methods ////////////////////////
1018
1054
  //////////////////////////////////////////////////////////
1019
1055
 
1056
+ _compile_template(template_name,schema_doc,doc_obj){
1057
+ /**
1058
+ * generates text for the doc by compiling the provided template.
1059
+ */
1060
+ try {
1061
+ if(!template_name||!schema_doc||!doc_obj){
1062
+ throw new Error("Incomplete info provided")
1063
+ }
1064
+ let template_info = schema_doc.settings?.text_templates[template_name]
1065
+ if(!template_info){
1066
+ throw Error("Template not found")
1067
+ }
1068
+ if (template_info.engine == "js_script") {
1069
+
1070
+ const runScript = (script, data) => {
1071
+ const cleanScript = script
1072
+ .replace(/\\n/g, '\n')
1073
+ .replace(/\\t/g, '\t')
1074
+ .trim();
1075
+
1076
+ if (!cleanScript) {
1077
+ throw new Error("Empty script provided");
1078
+ }
1079
+
1080
+ const funcBody = `
1081
+ "use strict";
1082
+ const {doc, schema} = arguments[0] || {};
1083
+ ${cleanScript}
1084
+ `;
1085
+
1086
+ try {
1087
+ const func = new Function(funcBody);
1088
+ const result = func(data);
1089
+ if (result && typeof result.text === 'string') {
1090
+ return result;
1091
+ }
1092
+ throw new Error("Script must return {text: '...'}");
1093
+ } catch (parseError) {
1094
+ throw new Error(`Script parse error: ${parseError.message}\nScript: ${cleanScript.substring(0, 100)}...`);
1095
+ }
1096
+ };
1097
+
1098
+ let result = runScript(template_info.template, {
1099
+ doc: doc_obj,
1100
+ schema: schema_doc,
1101
+ });
1102
+ // if (!result.text) {
1103
+ // throw new Error("js_script template must return {'text': '...'}");
1104
+ // }
1105
+ return result.text;
1106
+ } else if (this.utils.compile_template[template_info.engine]) {
1107
+ let result = this.utils.compile_template[template_info.engine](
1108
+ template_info.template,
1109
+ {
1110
+ doc: doc_obj,
1111
+ schema: schema_doc,
1112
+ },
1113
+ );
1114
+ if (!result.text) {
1115
+ throw new Error(`${template_name} template must return {'text': '...'}`);
1116
+ }
1117
+ return result.text
1118
+ } else {
1119
+ throw Error(
1120
+ `The engine ${template_info.engine} is not available in the current instance of BBDB.`,
1121
+ );
1122
+ }
1123
+
1124
+ } catch (error) {
1125
+ let text = `Unable to compile the template ${template_name}.Error: ${error.message}`
1126
+ return text
1127
+ }
1128
+ }
1129
+
1020
1130
 
1021
1131
  async _upgrade_schema_in_bulk(schemas,log_upgrade=false,log_message="Schema Upgrade in bulk"){
1022
1132
  // TODO add a check to now allow default system schema to be updated from this method
@@ -11,7 +11,7 @@ export const default_app = {
11
11
  active: true,
12
12
  description: "Meta-schema or the schema for defining other schemas",
13
13
  system_generated: true,
14
- version: 1.25,
14
+ version: 1.30,
15
15
  title: "Schema document",
16
16
  schema: {
17
17
  type: "object",
@@ -133,6 +133,39 @@ export const default_app = {
133
133
  default: "human",
134
134
  description:
135
135
  "The height and width should be 25px",
136
+ },
137
+ text_templates: {
138
+ "type": "object",
139
+ "title":"Text templates for the schema",
140
+ "description":"Define templates that compile and generate text for an individual document in the database",
141
+ "default":{ "json_string":{engine:"js_script","format":"plain",template: "const pretty = JSON.stringify(doc, null, 2);\nreturn {text: pretty};","description":"Generates a stringified JSON as plain text"} },
142
+ "patternProperties": {
143
+ "^[a-zA-Z_][a-zA-Z0-9_]*$": {
144
+ "type": "object",
145
+ "required": ["engine", "template"],
146
+ "properties": {
147
+ "engine": {
148
+ "type": "string",
149
+ "enum": ["handlebars","ejs","js_script","mustache"],
150
+ "description": "The templating engine used to render the template. js_script is when the template is code in javascript. All template compiler must returns an object `{text:'...'}`"
151
+ },
152
+ "format": {
153
+ "type": "string",
154
+ "description": "The output format of the template. Eg plain,markdown,html,tex"
155
+ },
156
+ "description": {
157
+ "type": "string",
158
+ "description": "A human-readable description of the template"
159
+ },
160
+ "template": {
161
+ "type": "string",
162
+ "description": "The actual template string (must be compatible with the selected engine). Assume that the engine has access to the following json: { doc:{meta, data, app, _schema, schema, id} , ... other related data}"
163
+ },
164
+ },
165
+ "additionalProperties": false
166
+ }
167
+ },
168
+ "additionalProperties": false,
136
169
  }
137
170
  },
138
171
  required: [
@@ -390,7 +423,7 @@ export const default_app = {
390
423
  schema: {
391
424
  type: "object",
392
425
  additionalProperties: true,
393
- required: ["script","type","version"],
426
+ required: ["script","type","version","name"],
394
427
  properties: {
395
428
  type: {
396
429
  type: "string",
@@ -1040,6 +1040,24 @@ describe("Doc read tests", async () => {
1040
1040
  let data = await database3.read({schema:"book",data:{"title":record_good_book1.title,"author":record_good_book1.author},include_schema:false})
1041
1041
  assert(Object.keys(data).length==1)
1042
1042
  })
1043
+
1044
+ it('check if view is not included', async () => {
1045
+ let data = await database3.read({schema:"book",data:{"title":record_good_book1.title,"author":record_good_book1.author}})
1046
+ assert(!data.view, "view property should not exist");
1047
+ })
1048
+
1049
+
1050
+ it('check if view is included even if template name not found', async () => {
1051
+ let data = await database3.read({schema:"book",data:{"title":record_good_book1.title,"author":record_good_book1.author},text_template:"json"})
1052
+ console.log(data)
1053
+ assert('view' in data);
1054
+ })
1055
+
1056
+ it('check if view is included , template name is found', async () => {
1057
+ let data = await database3.read({schema:"book",data:{"title":record_good_book1.title,"author":record_good_book1.author},text_template:"json_string"})
1058
+ console.log(data)
1059
+ assert('view' in data);
1060
+ })
1043
1061
  })
1044
1062
 
1045
1063
  /**
package/test/pouchdb.js CHANGED
@@ -122,6 +122,11 @@ const pdb = new PouchDB(dbname);
122
122
  const valid = validate(data_copy);
123
123
 
124
124
  return {valid,validate,data:data_copy}
125
+ },
126
+ "compile_template":{
127
+ "ejs":()=>{
128
+ return {text:""}
129
+ }
125
130
  }
126
131
  },
127
132
  }