beanbagdb 0.5.50 → 0.5.51

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.5.50",
3
+ "version": "0.5.51",
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",
@@ -34,5 +34,8 @@
34
34
  "nano": "^10.1.4",
35
35
  "pouchdb": "^9.0.0",
36
36
  "pouchdb-find": "^9.0.0"
37
+ },
38
+ "mocha": {
39
+ "spec": "test/**/*.test.js"
37
40
  }
38
41
  }
package/src/index.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import * as sys_sch from "./system_schema.js";
2
- // import { version } from "../package.json" assert {type :"json"};
3
2
  /**
4
3
  * This the core class. it is not very useful in itself but can be used to generate a sub class for a specific database for eg CouchDB.
5
4
  * It takes a db_instance argument, which , this class relies on perform CRUD operations on the data.
6
5
  * Why have a "dumb" class ? : So that the core functionalities remains in a single place and the multiple Databases can be supported.
6
+ * Naming convention :
7
+ * - user facing methods : verbs with underscores, no camel case
8
+ * - internal methods (uses the this object) only to be used within the class : name starts with underscore (_)
9
+ * - util methods : these can also be used by the user, this object not accessed, : name starts with util_
7
10
  */
8
- class BeanBagDB {
11
+ export class BeanBagDB {
9
12
  /**
10
13
  * @param {object} db_instance - Database object
11
14
  * db_instance object contains 3 main keys :
@@ -16,117 +19,196 @@ class BeanBagDB {
16
19
  */
17
20
  constructor(db_instance) {
18
21
  // data validation checks
19
- this._check_required_fields(["name", "encryption_key", "api", "utils"],db_instance)
20
- this._check_required_fields(["insert", "update", "delete", "search","get","createIndex"],db_instance.api)
21
- this._check_required_fields(["encrypt", "decrypt","ping","validate_schema"],db_instance.utils)
22
+ this.util_check_required_fields(["name", "encryption_key", "api", "utils","db_name"],db_instance)
23
+ this.util_check_required_fields(["insert", "update", "delete", "search","get","createIndex"],db_instance.api)
24
+ this.util_check_required_fields(["encrypt", "decrypt","ping","validate_schema"],db_instance.utils)
22
25
 
23
- if(db_instance.encryption_key.length>20){throw new Error("encryption_key must have at least 20 letters")}
26
+ if(db_instance.encryption_key.length<20){throw new Error("encryption_key must have at least 20 letters")}
24
27
  // db name should not be blank,
25
28
 
26
- this.name = db_instance.name;
27
29
  this.encryption_key = db_instance.encryption_key;
28
-
30
+ this.db_name = db_instance.db_name // couchdb,pouchdb etc...
29
31
  this.db_api = db_instance.api;
30
32
  this.utils = db_instance.utils;
31
33
 
32
- this._version = "0.5.0"
33
- this.ready_check = { initialized: false, latest: false };
34
- console.log("Run ready() now");
34
+ this.meta = {
35
+ database_name : db_instance.name,
36
+ backend_database : this.db_name,
37
+ beanbagdb_version_db : null
38
+ }
39
+
40
+ this._version = this._get_current_version()
41
+ // latest indicated if the DB was initialized with the latest version or not.
42
+ this.active = false
35
43
 
44
+ console.log("Run ready() now");
45
+
36
46
  this.plugins = {}
47
+
48
+ this.error_codes = {
49
+ not_active : "Database is not ready. Run ready() first",
50
+ schema_not_found:"Schema not found"
51
+ }
52
+ }
53
+
54
+ async metadata(){
55
+ // returns system data
56
+ return {
57
+ ... this.meta,
58
+ beanbagdb_version_code : this._version,
59
+ ready_to_use : this.active
60
+ }
61
+ // todo : doc count, schema count, records for each schema, size of the database,
37
62
  }
38
63
 
39
64
  /**
40
65
  * This is to check if the database is ready to be used. It it important to run this after the class is initialized.
41
66
  */
42
67
  async ready() {
43
- console.log("Checking...");
44
- // @TODO : ping the database
45
- // this._version = await getPackageVersion()
46
- this.ready_check = await this._check_ready_to_use();
47
- if (this.ready_check.initialized) {
48
- console.log("Ready to use!");
68
+ // TODO Ping db
69
+ let check = { initialized: false, latest: false ,db_version:null};
70
+ let version_search = await this.db_api.search({
71
+ selector: { schema: "system_settings", "data.name": "beanbagdb_version" },
72
+ });
73
+ if (version_search.docs.length > 0) {
74
+ let doc = version_search.docs[0];
75
+ this.active = doc["data"]["value"] == this._version;
76
+ this.meta.beanbagdb_version_db = doc["data"]["value"]
77
+ }
78
+ if(this.active){
79
+ console.log("Ready")
80
+ }else{
81
+ await this.initialize_db()
49
82
  }
50
83
  }
51
84
 
52
- check_if_ready(){
53
- return this.ready_check.ready
54
- }
85
+
55
86
 
56
87
  /**
57
88
  * Initializes the database making it ready to be used. Typically, required to run after every time package is updated to a new version.
58
89
  * See the documentation on the architecture of the DB to understand what default schemas are required for a smooth functioning of the database
59
90
  */
60
91
  async initialize_db() {
92
+ // this works on its own but is usually called by ready automatically if required
93
+
94
+ // check for schema_scehma : if yes, check if latest and upgrade if required, if no create a new schema doc
95
+ let logs = ["init started"]
61
96
  try {
62
- if (this.ready_check.initialized == false) {
63
- // add the meta-schemas doc
97
+ let schema = await this.get_schema_doc("schema")
98
+ if (schema["data"]["version"] != sys_sch.schema_schema.version){
99
+ logs.push("old schema_schema v "+schema["data"]["version"])
100
+ let full_doc = await this.db_api.get(schema["_id"])
101
+ full_doc["data"] = {...sys_sch.schema_schema}
102
+ full_doc["meta"]["updated_on"] = this._get_now_unix_timestamp()
103
+ await this.db_api.update(full_doc)
104
+ logs.push("new schema_schema v "+sys_sch.schema_schema.version)
105
+ }
106
+
107
+ } catch (error) {
108
+ console.log(error)
109
+ if (error.message==this.error_codes.schema_not_found) {
110
+ console.log("...adding new ")
111
+ // inserting new schema_schema doc
64
112
  let schema_schema_doc = this._get_blank_doc("schema");
65
113
  schema_schema_doc.data = sys_sch.schema_schema;
66
114
  await this.db_api.insert(schema_schema_doc);
67
- // add system schemas
68
- let keys = Object.keys(sys_sch.system_schemas);
69
- for (let index = 0; index < keys.length; index++) {
70
- const element = sys_sch.system_schemas[keys[index]];
71
- let schema_record = this._get_blank_schema_doc(
72
- "schema",
73
- sys_sch.schema_schema["schema"],
74
- element
75
- );
76
- await this.db_api.insert(schema_record);
115
+ logs.push("init schema_schema v "+sys_sch.schema_schema.version)
116
+ }
117
+ }
118
+
119
+ let keys = Object.keys(sys_sch.system_schemas);
120
+ for (let index = 0; index < keys.length; index++) {
121
+ const schema_name = sys_sch.system_schemas[keys[index]]["name"]
122
+ const schema_data = sys_sch.system_schemas[keys[index]];
123
+ try {
124
+ // console.log(schema_name)
125
+ let schema1 = await this.get_schema_doc(schema_name)
126
+ if (schema1["data"]["version"] != schema_data.version){
127
+ logs.push("old "+schema_name+" v "+schema1["data"]["version"])
128
+ let full_doc = await this.db_api.get(schema1["_id"])
129
+ full_doc["data"] = {...schema_data}
130
+ full_doc["meta"]["updated_on"] = this._get_now_unix_timestamp()
131
+ await this.db_api.update(full_doc)
132
+ logs.push("new "+schema_name+" v "+schema_data.version)
77
133
  }
78
- // create an index
79
- await this.db_api.createIndex({
80
- index: { fields: ["schema", "data", "meta"] },
81
- });
82
- console.log("Database Indexed.");
83
- // create the log doc
84
- const log_schema = sys_sch.system_schemas["logs"]["schema"];
85
- let log_doc = this._get_blank_schema_doc("system_logs", log_schema, {
86
- logs: [
87
- {
88
- message: `Database is initialized with version ${this._version}.`,
89
- on: this._get_now_unix_timestamp(),
90
- human_date: new Date().toLocaleString(),
91
- },
92
- ],
93
- });
94
- await this.db_api.insert(log_doc);
95
- // create the setting doc
96
- const setting_schema = sys_sch.system_schemas["settings"]["schema"];
97
- let setting_doc = this._get_blank_schema_doc(
98
- "system_settings",
99
- setting_schema,
100
- {
101
- name: "beanbagdb_version",
102
- value: this._version,
103
- user_editable: false,
134
+ } catch (error) {
135
+ console.log(error)
136
+ if (error.message==this.error_codes.schema_not_found) {
137
+ // inserting new schema doc
138
+ let new_schema_doc = this._get_blank_schema_doc("schema",sys_sch.schema_schema["schema"],schema_data);
139
+ await this.db_api.insert(new_schema_doc);
140
+ logs.push("init "+schema_name+" v "+schema_data.version)
141
+ }
142
+ }
143
+ }
144
+ // store the logs in the log_doc , generate it for the first time
145
+ // console.log(logs)
146
+ if(logs.length>1){
147
+ // version needs to be updated in the object as well as settings and must be logged
148
+ logs.push("Init done")
149
+
150
+ await this.insert_or_update_setting("system_logs",{value:{text:logs.join(","),added:this._get_now_unix_timestamp()},"on_update_array":"append"})
151
+ await this.insert_or_update_setting("beanbagdb_version",{value:this._version})
152
+ // await this.insert_or_update_setting("system_logs",{value:{text:"This is just a test.",added:this._get_now_unix_timestamp()}})
153
+
154
+ this.meta.beanbagdb_version_db = this._version
155
+ this.active = true
156
+ }else{
157
+ // no new updates were done
158
+ console.log("already updated. nothing is required to be done. continue")
159
+ }
160
+ }
161
+ async insert_or_update_setting(name,new_data,schema={}){
162
+ // TODO implement schema check
163
+ if(!new_data){throw new Error("No data provided")}
164
+ if(!new_data.value){throw new Error("No value provided")}
165
+
166
+ let doc_search = await this.db_api.search({"selector":{"schema":"system_settings","data.name":name}})
167
+ if(doc_search.docs.length>0){
168
+ // doc already exists, check schema and update it : if it exists then it's value already exists and can be
169
+ let doc = {...doc_search.docs[0]}
170
+ if(Array.isArray(doc.data.value)){
171
+ let append_type = doc.data.on_update_array
172
+ if(append_type=="append"){
173
+ doc["data"]["value"].push(new_data.value)
174
+ }else if(append_type=="update"){
175
+ doc["data"]["value"] = new_data.value
176
+ }else{
177
+ throw new Error("Invalid on update array value")
104
178
  }
105
- );
106
- await this.db_api.insert(setting_doc);
107
- // finally update the flags
108
- this.ready_check.initialized = true;
109
- this.ready_check.latest = true;
110
- console.log("Database initialized");
111
- } else {
112
- console.log("Database already initialized");
113
- if (!this.ready_check.latest) {
114
- // update to latest schema
115
- this._update_system_schema();
116
- } else {
117
- console.log("Database already up to date");
179
+ }else{
180
+ doc["data"]["value"] = new_data.value
118
181
  }
182
+ // finally update it
183
+ doc["meta"]["updated_on"] = this._get_now_unix_timestamp()
184
+ await this.db_api.update(doc)
185
+ return doc
186
+
187
+ }else{
188
+ // doc does not exists, generate a new one
189
+ let new_val= {value:new_data.value}
190
+
191
+ if (new_data.on_update_array){
192
+ // this indicates the provided value is initial value inside the array
193
+ new_val.value = [new_data.value]
194
+ new_val.on_update_array = new_data.on_update_array
119
195
  }
120
- } catch (error) {
121
- console.log(error);
122
- throw error;
196
+ let new_doc = this._get_blank_doc("system_settings")
197
+ new_doc["data"] = {
198
+ "name": name,
199
+ ...new_val
200
+ }
201
+ let d = await this.db_api.insert(new_doc)
202
+ return d
123
203
  }
124
204
  }
125
205
 
206
+
126
207
  /**
127
208
  * Adds indexes for all the schemas in the data base. This is important to make search faster. This must be done every time a new schema is introduced in the database
128
209
  */
129
210
  async update_indexes() {
211
+ this._check_ready_to_use()
130
212
  // @TODO check this. i don't the index created this way are actually useful in search.
131
213
  let all_schemas_docs = await this.db_api.search({
132
214
  selector: { schema: "schema" },
@@ -153,41 +235,74 @@ class BeanBagDB {
153
235
  //const validate = ajv.compile(schema_obj);
154
236
  //const valid = validate(data_obj);
155
237
  if (!valid) {
156
- console.log(validate.errors);
157
- throw new Error(validate.errors);
238
+ throw new ValidationError(validate.errors);
158
239
  }
159
240
  }
160
241
 
161
242
  validate_schema_object(schema_doc){
162
- let errors = []
243
+ let errors = [{"message":"Schema validation errors "}]
163
244
  if(!schema_doc["schema"]["type"]){
164
- errors.push("Schema must have the field schema.'type' which can only be 'object' ")
245
+ errors.push({message:"Schema must have the field schema.'type' which can only be 'object' "})
165
246
  }else{
166
247
  if(schema_doc["schema"]["type"]!="object"){
167
- errors.push("The schema.'type' value is invalid.Only 'object' allowed")
248
+ errors.push({message:"The schema.'type' value is invalid.Only 'object' allowed"})
168
249
  }
169
250
  }
170
251
  if(!schema_doc["schema"]["properties"]){
171
- errors.push("The schema.'properties' object does not exists")
252
+ errors.push({message:"The schema.'properties' object does not exists"})
172
253
  }else{
173
254
  if(typeof(schema_doc["schema"]["properties"])!="object"){
174
- errors.push("Invalid schema.properties. It must be an object and must have atleast one field inside.")
255
+ errors.push({message:"Invalid schema.properties. It must be an object and must have atleast one field inside."})
175
256
  }
176
257
  if(Object.keys(schema_doc["schema"]["properties"]).length==0){
177
- errors.push("You must define at least one property")
258
+ errors.push({message:"You must define at least one property"})
178
259
  }
179
260
  }
180
261
 
181
262
  if(!schema_doc["schema"]["additionalProperties"]){
182
- errors.push("The schema.'additionalProperties' field is required")
263
+ errors.push({message:"The schema.'additionalProperties' field is required"})
183
264
  }else{
184
265
  if(typeof(schema_doc["schema"]["additionalProperties"])!="boolean"){
185
- errors.push("Invalid schema.additionalProperties. It must be a boolean value")
266
+ errors.push({message:"Invalid schema.additionalProperties. It must be a boolean value"})
267
+ }
268
+ }
269
+
270
+ const allKeys = Object.keys(schema_doc["schema"]["properties"])
271
+ if(schema_doc["settings"]["primary_keys"].length>0){
272
+ // check if all keys belong to the schema and are not of type object
273
+ let all_pk_exist = schema_doc["settings"]["primary_keys"].every(item=>allKeys.includes(item)&&schema_doc["schema"]["properties"][item]["type"]!="object"&&schema_doc["schema"]["properties"][item]["type"]!="array")
274
+
275
+ if(!all_pk_exist){
276
+ errors.push({message:"Primary keys invalid. All keys must be defined in the schema and must be non object"})
277
+ }
278
+ }
279
+
280
+
281
+ if(schema_doc["settings"]["non_editable_fields"].length>0){
282
+ // check if all keys belong to the schema
283
+ let all_ne_exist = schema_doc["settings"]["non_editable_fields"].every(item=>allKeys.includes(item))
284
+ if(!all_ne_exist){
285
+ errors.push({message:"Non editable fields invalid. All fields must be defined in the schema "})
186
286
  }
187
287
  }
188
288
 
189
- if(errors.length>0){
190
- throw new Error("Schema validation errors- "+errors.join(","))
289
+ if(schema_doc["settings"]["encrypted_fields"].length>0){
290
+ // check if all keys belong to the schema and are only string
291
+ let all_enc_exist = schema_doc["settings"]["encrypted_fields"].every(item=>allKeys.includes(item)&&schema_doc["schema"]["properties"][item]["type"]=="string")
292
+ if(!all_enc_exist){
293
+ errors.push({message:"Invalid encrypted fields. All fields must be defined in the schema and must be string "})
294
+ }
295
+
296
+ // check : primary keys cannot be encrypted
297
+ let all_enc_no_pk = schema_doc["settings"]["encrypted_fields"].every(item=>!schema_doc["settings"]["primary_keys"].includes(item))
298
+ if(!all_enc_no_pk){
299
+ errors.push({message:"Invalid encrypted fields.Primary key fields cannot be encrypted "})
300
+ }
301
+ }
302
+
303
+ /// cannot encrypt primary field keys
304
+ if(errors.length>1){
305
+ throw new ValidationError(errors)
191
306
  }
192
307
  }
193
308
 
@@ -198,9 +313,10 @@ class BeanBagDB {
198
313
  * @returns {Object} {doc} or {doc,schema}
199
314
  */
200
315
  async get(doc_id,include_schema=false) {
316
+ this._check_ready_to_use()
201
317
  let doc = await this.db_api.get(doc_id);
202
318
  let schema = await this.get_schema_doc(doc.schema);
203
- doc = this._decrypt_doc(schema, doc);
319
+ doc = this._decrypt_doc(schema["data"], doc);
204
320
  if(include_schema){
205
321
  return {doc,schema}
206
322
  }
@@ -215,10 +331,11 @@ class BeanBagDB {
215
331
  let schemaSearch = await this.db_api.search({
216
332
  selector: { schema: "schema", "data.name": schema_name },
217
333
  });
334
+ // console.log(schemaSearch)
218
335
  if (schemaSearch.docs.length == 0) {
219
- throw new Error("Schema not found");
336
+ throw new Error(this.error_codes.schema_not_found);
220
337
  }
221
- return schemaSearch.docs[0]["data"];
338
+ return schemaSearch.docs[0];
222
339
  }
223
340
 
224
341
  /**
@@ -230,7 +347,9 @@ class BeanBagDB {
230
347
  * @returns object
231
348
  */
232
349
  async get_doc(schema_name, primary_key = {}) {
233
- let s_doc = await this.get_schema_doc(schema_name);
350
+ this._check_ready_to_use()
351
+ let schema_doc = await this.get_schema_doc(schema_name);
352
+ let s_doc = schema_doc["data"];
234
353
  let doc_obj;
235
354
  if (
236
355
  s_doc["settings"]["primary_keys"] &&
@@ -268,6 +387,7 @@ class BeanBagDB {
268
387
  * @param {Object} criteria
269
388
  */
270
389
  async search(criteria) {
390
+ this._check_ready_to_use()
271
391
  if (!criteria["selector"]) {
272
392
  throw new Error("Invalid search query.");
273
393
  }
@@ -285,12 +405,14 @@ class BeanBagDB {
285
405
  * @param {Object} settings (optional)
286
406
  */
287
407
  async insert(schema, data, meta= {},settings = {}) {
408
+ //console.log("here in insert")
409
+ this._check_ready_to_use()
288
410
  try {
289
411
  let doc_obj = await this._insert_pre_checks(schema, data, settings);
290
412
  let new_rec = await this.db_api.insert(doc_obj);
291
413
  return { id: new_rec["id"] };
292
414
  } catch (error) {
293
- console.log(error);
415
+ // console.log(error);
294
416
  throw error;
295
417
  }
296
418
  }
@@ -312,13 +434,14 @@ class BeanBagDB {
312
434
  * @param {*} schema_name
313
435
  * @param {doc_obj} updates {data:{},meta:{}}, need not be the full document, just the new values of all/some fields
314
436
  * @param {Boolean} save_conflict = true -
315
- * @returns
437
+ *
316
438
  */
317
439
  async update(doc_id, rev_id, updates, update_source="api",save_conflict = true) {
440
+ this._check_ready_to_use()
318
441
  // making a big assumption here : primary key fields cannot be edited
319
442
  // so updating the doc will not generate primary key conflicts
320
443
  let req_data = await this.get(doc_id,true);
321
- let schema = req_data.schema // await this.get_schema_doc(schema_name);
444
+ let schema = req_data.schema
322
445
  let full_doc = req_data.doc // await this.get(doc_id)["doc"];
323
446
 
324
447
  // @TODO fix this : what to do if the rev id does not match
@@ -329,43 +452,37 @@ class BeanBagDB {
329
452
  // }
330
453
  // }
331
454
 
332
- // blank check
333
-
334
455
  // update new value depending on settings.editable_fields (if does not exists, all fields are editable)
335
- let edit_fields = Object.keys(schema.schema.properties)
336
- if(schema.settings["editable_fields"]&&schema.settings["editable_fields"].length>0){
337
- edit_fields = schema.settings["editable_fields"]
338
- }
456
+ let all_fields = Object.keys(schema.schema.properties)
457
+ let unedit_fields = schema.settings["non_editable_fields"]
458
+ let edit_fields = all_fields.filter(item=>!unedit_fields.includes(item))
339
459
 
340
460
  // now generate the new doc with updates
341
461
  let allowed_updates = this._filterObject(updates.data,edit_fields);
342
462
  let updated_data = { ...full_doc.data, ...allowed_updates };
343
463
 
344
- // validate data
345
464
  this.validate_data(schema.schema, updated_data);
346
465
 
347
466
  // primary key check if multiple records can be created
348
- if(schema.settings["single_record"]==false){
349
- if(schema.settings["primary_keys"]&&schema.settings["primary_keys"].length>0){
350
- let pri_fields = schema.settings["primary_keys"]
351
- let search_criteria = {schema:schema.name}
352
- pri_fields.map(itm=>{search_criteria["data."+itm] = updated_data[itm]})
353
- let search = await this.search({selection:search_criteria})
354
- if(search.docs.length>0){
355
- if(search.docs.length==1){
356
- let thedoc = search.docs[0]
357
- if(thedoc["_id"]!=doc_id){
358
- throw new Error("Update not allowed. Document with the same primary key already exists")
359
- }
360
- }else{
361
- throw new Error("There is something wrong with the schema")
467
+ if(schema.settings["single_record"]==false && schema.settings["primary_keys"].length>0){
468
+ let pri_fields = schema.settings["primary_keys"]
469
+ let search_criteria = {schema:schema.name}
470
+ pri_fields.map(itm=>{search_criteria["data."+itm] = updated_data[itm]})
471
+ let search = await this.search({selection:search_criteria})
472
+ if(search.docs.length>0){
473
+ if(search.docs.length==1){
474
+ let thedoc = search.docs[0]
475
+ if(thedoc["_id"]!=doc_id){
476
+ throw new DocUpdateError([{message:"Update not allowed. Document with the same primary key already exists"}])
362
477
  }
363
478
  }
364
- }
479
+ else{
480
+ throw new Error("There is something wrong with the schema primary keys")
481
+ }
482
+ }
365
483
  }
366
484
 
367
485
  // encrypt the data
368
-
369
486
  full_doc["data"] = updated_data
370
487
  full_doc = this._encrypt_doc(schema,full_doc);
371
488
 
@@ -384,11 +501,13 @@ class BeanBagDB {
384
501
  }
385
502
 
386
503
  async delete(doc_id) {
504
+ this._check_ready_to_use()
387
505
  await this.db_api.delete(doc_id)
388
506
  }
389
507
 
390
508
 
391
509
  async load_plugin(plugin_name,plugin_module){
510
+ this._check_ready_to_use()
392
511
  this.plugins[plugin_name] = {}
393
512
  for (let func_name in plugin_module){
394
513
  if(typeof plugin_module[func_name]=='function'){
@@ -401,24 +520,39 @@ class BeanBagDB {
401
520
  }
402
521
  }
403
522
 
404
- //////// Helper method ////////
523
+ //////// Internal methods ////////
524
+
525
+ _get_current_version(){
526
+ // current version is the sum of versions of all system defined schemas
527
+ let sum = sys_sch.schema_schema.version
528
+ let keys = Object.keys(sys_sch.system_schemas).map(item=>{
529
+ sum = sum+ sys_sch.system_schemas[item].version
530
+ })
531
+ if(sum == NaN){
532
+ throw Error("Error in system schema version numbers")
533
+ }
534
+ return sum
535
+ }
536
+
537
+ _check_ready_to_use(){
538
+ if(!this.active){
539
+ throw new Error(this.error_codes.not_active)
540
+ }
541
+ }
542
+
405
543
 
406
544
  _generate_random_link(){
407
545
  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'];
408
546
  return Array.from({ length: 4 }, () => dictionary[Math.floor(Math.random() * dictionary.length)]).join('-');
409
547
  }
410
548
 
411
- _check_required_fields(requiredFields,obj){
412
- for (const field of requiredFields) {
413
- if (!obj[field]) {throw new Error(`${field} is required`);}
414
- }
415
- }
549
+
416
550
 
417
551
  /**
418
552
  *
419
553
  * @param {*} obj
420
554
  * @param {*} fields
421
- * @returns
555
+ *
422
556
  */
423
557
  _filterObject(obj, fields) {
424
558
  return fields.reduce((filteredObj, field) => {
@@ -429,36 +563,7 @@ class BeanBagDB {
429
563
  }, {});
430
564
  }
431
565
 
432
- /**
433
- * Checks if the selected database is initialized for working with BeanBagDB. Also throws a warning if package version does not match with database version.
434
- * Every time a database is initialized, a setting document `beanbagdb_version` is added. If this does not exists, the database is not initialized. If it exists but does not match the current version, a warning is shown.
435
- * @returns {object} {initialized:boolean,latest:boolean}
436
- */
437
- async _check_ready_to_use() {
438
- // @TODO check if ready to use in major API methods
439
- let check = { initialized: false, latest: false };
440
- // @TODO this is not really fool proof. check all the required docs, they have the system_generated flag
441
- // what if some user mistakenly modifies or deletes some of the required docs ?
442
- let version_search = await this.db_api.search({
443
- selector: { schema: "system_settings", "data.name": "beanbagdb_version" },
444
- });
445
- if (version_search.docs.length > 0) {
446
- let doc = version_search.docs[0];
447
- check.initialized = true;
448
- check.latest = doc["data"]["value"] == this._version;
449
- }
450
- if (check.initialized == false) {
451
- console.warn(
452
- "This database is not ready to be used. It is not initialized. Run `initialize_db()` first"
453
- );
454
- }
455
- if ((check.latest == false) & (check.initialized == true)) {
456
- console.warn(
457
- "This database is not updated with the latest version. Run `initialize_db()` again to update to the latest version"
458
- );
459
- }
460
- return check;
461
- }
566
+
462
567
 
463
568
  /**
464
569
  * To update the system schema or reset to a stable version to ensure functioning of the BeanBagDB
@@ -488,7 +593,7 @@ class BeanBagDB {
488
593
  let doc = {
489
594
  data: {},
490
595
  meta: {
491
- createdOn: this._get_now_unix_timestamp(),
596
+ created_on: this._get_now_unix_timestamp(),
492
597
  tags: [],
493
598
  app :{},
494
599
  link : this._generate_random_link() // there is a link by default. overwrite this if user provided one but only before checking if it is unique
@@ -629,6 +734,54 @@ class BeanBagDB {
629
734
  doc_obj["data"] = new_data;
630
735
  return doc_obj;
631
736
  }
737
+
738
+ ////// Utility methods
739
+ util_check_required_fields(requiredFields,obj){
740
+ for (const field of requiredFields) {
741
+ if (!obj[field]) {throw new Error(`${field} is required`);}
742
+ }
743
+ }
632
744
  }
633
745
 
634
- export default BeanBagDB;
746
+
747
+ export class ValidationError extends Error {
748
+ constructor(errors = []) {
749
+ // Create a message based on the list of errors
750
+ //console.log(errors)
751
+ let error_messages = errors.map(item=>` ${(item.instancePath||" ").replace("/","")} ${item.message} `)
752
+ let message = `Validation failed with ${errors.length} error(s): ${error_messages.join(",")}`;
753
+ super(message);
754
+ this.name = 'ValidationError';
755
+ this.errors = errors; // Store the list of errors
756
+ }
757
+ }
758
+
759
+ export class DocUpdateError extends Error {
760
+ constructor(errors=[]){
761
+ let error_messages = errors.map(item=>`${item.message}`)
762
+ let message = `Error in document update. ${error_messages.join(",")}`
763
+ super(message)
764
+ this.name = "DocUpdateError";
765
+ this.errors = errors
766
+ }
767
+ }
768
+
769
+ export class DocInsertError extends Error {
770
+ constructor(errors=[]){
771
+ let error_messages = errors.map(item=>`${item.message}`)
772
+ let message = `Error in document insert. ${error_messages.join(",")}`
773
+ super(message)
774
+ this.name = "DocInsertError";
775
+ this.errors = errors
776
+ }
777
+ }
778
+
779
+ export class DocNotFoundError extends Error {
780
+ constructor(errors=[]){
781
+ let error_messages = errors.map(item=>`${item.message}`)
782
+ let message = `Error in fetching document. Criteria : ${error_messages.join(",")}`
783
+ super(message)
784
+ this.name = "DocNotFoundError";
785
+ this.errors = errors
786
+ }
787
+ }
@@ -1,7 +1,8 @@
1
1
  export const schema_schema = {
2
2
  name: "schema",
3
- description:"Meta-schema or schema for defining other schemas",
3
+ description:"Meta-schema or the schema for defining other schemas",
4
4
  system_generated:true,
5
+ version:0.5,
5
6
  schema: {
6
7
  type: "object",
7
8
  additionalProperties: false,
@@ -10,6 +11,12 @@ export const schema_schema = {
10
11
  type:"boolean",
11
12
  default:false
12
13
  },
14
+ version: {
15
+ type: "number",
16
+ minimum: 0,
17
+ default: 1,
18
+ description:"This is an optional field.To be used primarily for system schemas"
19
+ },
13
20
  name: {
14
21
  type: "string",
15
22
  minLength: 5,
@@ -41,14 +48,17 @@ export const schema_schema = {
41
48
  type: "string",
42
49
  },
43
50
  maxItems: 10,
51
+ description:"Fields that makes each document unique in the schema.Leave it blank if you do not need it. You can still be able to distinguish documents using the link field and the document id."
44
52
  },
45
- editable_fields: {
53
+ non_editable_fields: {
46
54
  type: "array",
47
55
  default: [],
48
56
  items: {
49
57
  type: "string",
50
58
  },
51
59
  maxItems: 50,
60
+ minItems:0,
61
+ description:"The list of fields whose values are added when the document is created but cannot be edited later in future."
52
62
  },
53
63
  encrypted_fields: {
54
64
  type: "array",
@@ -57,6 +67,7 @@ export const schema_schema = {
57
67
  type: "string",
58
68
  },
59
69
  maxItems: 50,
70
+ description:"Once set, all the data in this field will be encrypted before storing it in the database. Encryption key must be provided during the time of BeanBagDB initialization and must be managed by the user as it is NOT stored in the database"
60
71
  },
61
72
  single_record: {
62
73
  type: "boolean",
@@ -65,6 +76,7 @@ export const schema_schema = {
65
76
  "If set, only a single records with this schema will be allowed to insert in the database",
66
77
  },
67
78
  },
79
+ required :["primary_keys","non_editable_fields","single_record","encrypted_fields"]
68
80
  },
69
81
  },
70
82
  required: ["name","description","schema", "settings"],
@@ -76,29 +88,9 @@ export const schema_schema = {
76
88
  };
77
89
 
78
90
  export const system_schemas = {
79
- logs: {
80
- system_generated:true,
81
- description:"Schema for the log doc. Single log doc for the whole DB to log stuff about the DB",
82
- name: "system_logs",
83
- schema: {
84
- type: "object",
85
- additionalProperties: true,
86
- properties: {
87
- logs: {
88
- type: "array",
89
- items: {
90
- type: "object",
91
- additionalProperties: true,
92
- },
93
- },
94
- },
95
- },
96
- settings: {
97
- single_record: true
98
- },
99
- },
100
91
  keys: {
101
92
  system_generated:true,
93
+ version:0.5,
102
94
  description:"To store user defined key. this can include anything like API tokens etc. There is a special method to fetch this. The values are encrypted",
103
95
  name: "system_keys",
104
96
  schema: {
@@ -129,10 +121,13 @@ export const system_schemas = {
129
121
  },
130
122
  settings: {
131
123
  primary_keys: ["name"],
132
- encrypted_fields:["value"]
124
+ encrypted_fields:["value"],
125
+ non_editable_fields:[],
126
+ single_record: false
133
127
  },
134
128
  },
135
129
  settings: {
130
+ version:0.5,
136
131
  system_generated:true,
137
132
  description:"The system relies on these settings for proper functioning or enabling optional features.",
138
133
  name: "system_settings",
@@ -144,27 +139,25 @@ export const system_schemas = {
144
139
  name: {
145
140
  type: "string",
146
141
  minLength: 5,
147
- maxLength: 250,
142
+ maxLength: 1000,
148
143
  pattern: "^[a-zA-Z][a-zA-Z0-9_]*$",
149
144
  },
150
145
  value: {
151
- type: "string",
152
- minLength: 5,
153
- maxLength: 5000,
154
- pattern: "^[^\n\r]*$",
155
- description:"Must be a single line string"
156
- },
157
- user_editable: {
158
- type: "boolean",
159
- default: true,
160
- description:
161
- "Whether this setting is editable by the user or only by the system",
146
+ type: ["string", "number", "boolean", "array"]
162
147
  },
148
+ on_update_array:{
149
+ type:"string",
150
+ default:"replace",
151
+ description:"Special operation only for updating Arrays. Either replace it or append new elements to it. Cannot be edited",
152
+ enum:["replace","append"],
153
+ }
163
154
  },
164
155
  },
165
156
  settings: {
166
157
  primary_keys: ["name"],
167
- editable_fields: ["value"],
158
+ non_editable_fields: ["name"],
159
+ encrypted_fields:[],
160
+ single_record:false
168
161
  },
169
162
  },
170
163
  };
@@ -0,0 +1,29 @@
1
+ import * as cdb from "./couchdb.js"
2
+ import "dotenv/config"
3
+
4
+
5
+
6
+
7
+
8
+ (async()=>{
9
+
10
+ let db = new cdb.BeanBagDB_CouchDB(process.env.cdburl, process.env.cdbname, process.env.secret)
11
+
12
+ //db.initialize_db()
13
+ await db.ready()
14
+ console.log(await db.metadata())
15
+ let test_schema = {
16
+ "name":"contact_list",
17
+ "description":"My contact book",
18
+ "schema":{},
19
+ "settings":{}
20
+ }
21
+
22
+ //let sch = await db.insert("schema",test_schema,{"link":"test1"})
23
+ //console.log(sch)
24
+
25
+
26
+ })();
27
+
28
+
29
+
@@ -0,0 +1,75 @@
1
+ // const crypto = require('crypto');
2
+ // const SDB = require("beanbagdb")
3
+
4
+ import Ajv from 'ajv';
5
+ import nano from "nano";
6
+ import BeanBagDB from '../src/index.js';
7
+
8
+ export class BeanBagDB_CouchDB extends BeanBagDB {
9
+ constructor(db_url,db_name,encryption_key){
10
+ //const cdb = import("nano")(db_url)
11
+ const cdb = nano(db_url)
12
+ const doc_obj = {
13
+ db_name:"couchdb",
14
+ name: db_name,
15
+ encryption_key: encryption_key,
16
+ api:{
17
+ insert: async (doc)=>{
18
+ const result = await cdb.insert(doc)
19
+ return result
20
+ },
21
+ // delete: ()=>{db1.destroy},
22
+ update: async (doc)=>{
23
+ const result = await cdb.insert(doc)
24
+ return result
25
+ },
26
+ search: async (query)=>{
27
+ const results = await cdb.find(query)
28
+ return results // of the form {docs:[],...}
29
+ },
30
+ get: async (id)=>{
31
+ const data = await cdb.get(id)
32
+ return data
33
+ },
34
+ createIndex: async (filter)=>{
35
+ const data = await cdb.createIndex(filter)
36
+ return data
37
+ },
38
+ delete: async (doc_id)=>{
39
+ const data = await cdb.get(id)
40
+ await cdb.destroy(data._id,data._rev)
41
+ }
42
+ },
43
+ utils:{
44
+ encrypt: (text,encryptionKey)=>{
45
+ const key = crypto.scryptSync(encryptionKey, 'salt', 32); // Derive a 256-bit key
46
+ const iv = crypto.randomBytes(16); // Initialization vector
47
+ const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
48
+ let encrypted = cipher.update(text, 'utf8', 'hex');
49
+ encrypted += cipher.final('hex');
50
+ return iv.toString('hex') + ':' + encrypted; // Prepend the IV for later use
51
+ },
52
+ decrypt : (encryptedText, encryptionKey)=>{
53
+ const key = crypto.scryptSync(encryptionKey, 'salt', 32); // Derive a 256-bit key
54
+ const [iv, encrypted] = encryptedText.split(':').map(part => Buffer.from(part, 'hex'));
55
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
56
+ let decrypted = decipher.update(encrypted, 'hex', 'utf8');
57
+ decrypted += decipher.final('utf8');
58
+ return decrypted;
59
+ },
60
+ ping : ()=>{
61
+ // @TODO ping the database to check connectivity when class is ready to use
62
+ },
63
+ validate_schema: (schema_obj, data_obj)=>{
64
+ const ajv = new Ajv({code: {esm: true}}) // options can be passed, e.g. {allErrors: true}
65
+ const validate = ajv.compile(schema_obj);
66
+ const valid = validate(data_obj);
67
+ return {valid,validate}
68
+ }
69
+ }
70
+ }
71
+ super(doc_obj)
72
+ }
73
+ }
74
+
75
+
package/test/init.test.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // to test initialization of the BeanBagDB class
2
2
  import { get_pdb_doc } from './pouchdb.js';
3
3
  import { throws, strictEqual } from "assert";
4
- import BeanBagDB from '../src/index.js';
4
+ import {BeanBagDB} from '../src/index.js';
5
5
 
6
6
  /**
7
7
  * Initial setup
@@ -2,9 +2,7 @@
2
2
  // to test initialization of the BeanBagDB class
3
3
  import { get_pdb_doc } from './pouchdb.js';
4
4
  import { throws, strictEqual,rejects } from "assert";
5
- import BeanBagDB from '../src/index.js';
6
-
7
-
5
+ import {BeanBagDB,ValidationError} from '../src/index.js';
8
6
 
9
7
  let database // this is the global db object
10
8
 
@@ -20,60 +18,64 @@ describe("Successful database class init (required for further testing) ", async
20
18
  });
21
19
  });
22
20
 
23
-
24
-
25
-
26
- describe("Testing creation of schema docs", async () => {
21
+ describe("Schema doc insertion gives errors when", async () => {
27
22
  let schema_docs_invalid = [
28
- {
23
+ [ "name missing",
24
+ {
29
25
  name: "",
30
26
  description: "",
31
27
  schema: {},
32
28
  settings: {},
33
- },
34
- {
29
+ }],
30
+ ["name is too short ",
31
+ {
35
32
  name: "nos",
36
33
  description: "",
37
34
  schema: {},
38
35
  settings: {},
39
- },
40
- {
36
+ }],
37
+ ["schema is blank",
38
+ {
41
39
  name: "contact",
42
40
  description: "",
43
41
  schema: {},
44
42
  settings: {},
45
- },
46
- {
43
+ }],
44
+ ["schema object missing",
45
+ {
47
46
  name: "contact",
48
47
  description: "This can be left blank",
49
- schema: {},
50
48
  settings: {},
51
- },
52
- {
49
+ }],
50
+ [
51
+ "no schema.type",{
53
52
  name: "contact",
54
53
  description: "This can be left blank",
55
54
  schema: {
56
55
  "abc":"something"
57
56
  },
58
57
  settings: {},
59
- },
60
- {
58
+ }],
59
+ ["schema.type is invalid",
60
+ {
61
61
  name: "contact",
62
62
  description: "This can be left blank",
63
63
  schema: {
64
64
  "type":"something",
65
65
  },
66
66
  settings: {},
67
- },
68
- {
67
+ }],
68
+ ["schema.properties is missing",
69
+ {
69
70
  name: "contact",
70
71
  description: "This can be left blank",
71
72
  schema: {
72
73
  "type":"object",
73
74
  },
74
75
  settings: {},
75
- },
76
- {
76
+ }],
77
+ ["schema.properties is invalid",
78
+ {
77
79
  name: "contact",
78
80
  description: "This can be left blank",
79
81
  schema: {
@@ -81,8 +83,9 @@ describe("Testing creation of schema docs", async () => {
81
83
  "properties":"something"
82
84
  },
83
85
  settings: {},
84
- },
85
- {
86
+ }],
87
+ ["schema.properties are missing/blank object",
88
+ {
86
89
  name: "contact",
87
90
  description: "This can be left blank",
88
91
  schema: {
@@ -90,8 +93,9 @@ describe("Testing creation of schema docs", async () => {
90
93
  "properties":{}
91
94
  },
92
95
  settings: {},
93
- },
94
- {
96
+ }],
97
+ ["schema.additionalProperties is missing",
98
+ {
95
99
  name: "contact",
96
100
  description: "This can be left blank",
97
101
  schema: {
@@ -99,8 +103,9 @@ describe("Testing creation of schema docs", async () => {
99
103
  "properties":{"name":{"type":"string"}}
100
104
  },
101
105
  settings: {},
102
- },
103
- {
106
+ }],
107
+ ["schema.additionalProperties is invalid",
108
+ {
104
109
  name: "contact",
105
110
  description: "This can be left blank",
106
111
  schema: {
@@ -109,8 +114,30 @@ describe("Testing creation of schema docs", async () => {
109
114
  "additionalProperties":"no"
110
115
  },
111
116
  settings: {},
112
- },
113
- // {
117
+ }],
118
+ [
119
+ "setting is missing",{
120
+ name: "contact",
121
+ description: "This can be left blank",
122
+ schema: {
123
+ "type":"object",
124
+ "properties":{"name":{"type":"string"}},
125
+ "additionalProperties":true
126
+ }
127
+ }],
128
+ [
129
+ "setting is invalid",{
130
+ name: "contact",
131
+ description: "This can be left blank",
132
+ schema: {
133
+ "type":"object",
134
+ "properties":{"name":{"type":"string"}},
135
+ "additionalProperties":true
136
+ },
137
+ settings:"none"
138
+ }],
139
+ // ["settings.primary_keys is missing",
140
+ // {
114
141
  // name: "contact",
115
142
  // description: "This can be left blank",
116
143
  // schema: {
@@ -118,28 +145,183 @@ describe("Testing creation of schema docs", async () => {
118
145
  // "properties":{"name":{"type":"string"}},
119
146
  // "additionalProperties":true
120
147
  // },
121
- // settings: {},
122
- // },
148
+ // settings: {
149
+ // },
150
+ // }],
151
+ ["settings.primary_keys is invalid",
152
+ {
153
+ name: "contact",
154
+ description: "This can be left blank",
155
+ schema: {
156
+ "type":"object",
157
+ "properties":{"name":{"type":"string"}},
158
+ "additionalProperties":true
159
+ },
160
+ settings: {
161
+ primary_keys:"name"
162
+ },
163
+ }],
164
+ ["settings.non_editable_fields is invalid",
165
+ {
166
+ name: "contact",
167
+ description: "This can be left blank",
168
+ schema: {
169
+ "type":"object",
170
+ "properties":{"name":{"type":"string"}},
171
+ "additionalProperties":true
172
+ },
173
+ settings: {
174
+ primary_keys:["name"],
175
+ non_editable_fields:"all"
176
+ },
177
+ }],
178
+ ["settings.single_record is invalid",
179
+ {
180
+ name: "contact",
181
+ description: "This can be left blank",
182
+ schema: {
183
+ "type":"object",
184
+ "properties":{"name":{"type":"string"}},
185
+ "additionalProperties":true
186
+ },
187
+ settings: {
188
+ primary_keys:["name"],
189
+ non_editable_fields:[],
190
+ single_record:"no"
191
+ },
192
+ }],
193
+ ["settings.encrypted_fields is invalid",
194
+ {
195
+ name: "contact",
196
+ description: "This can be left blank",
197
+ schema: {
198
+ "type":"object",
199
+ "properties":{"name":{"type":"string"}},
200
+ "additionalProperties":true
201
+ },
202
+ settings: {
203
+ primary_keys:["name"],
204
+ non_editable_fields:[],
205
+ single_record:false,
206
+ encrypted_fields:"none"
207
+ },
208
+ }],
209
+ ["settings.primary_keys fields are not defined in schema",
210
+ {
211
+ name: "contact",
212
+ description: "This can be left blank",
213
+ schema: {
214
+ "type":"object",
215
+ "properties":{"name":{"type":"string"}},
216
+ "additionalProperties":true
217
+ },
218
+ settings: {
219
+ primary_keys:["name1"],
220
+ non_editable_fields:[],
221
+ single_record:false,
222
+ encrypted_fields:"none"
223
+ },
224
+ }],
225
+ ["settings.primary_keys field is an object",
226
+ {
227
+ name: "contact",
228
+ description: "This can be left blank",
229
+ schema: {
230
+ "type":"object",
231
+ "properties":{"name":{"type":"object"},"address":{type:"string"}},
232
+ "additionalProperties":true
233
+ },
234
+ settings: {
235
+ primary_keys:["name"],
236
+ non_editable_fields:[],
237
+ single_record:false,
238
+ encrypted_fields:[]
239
+ },
240
+ }],
241
+ ["settings.non_editable_fields not defined in the schema",
242
+ {
243
+ name: "contact",
244
+ description: "This can be left blank",
245
+ schema: {
246
+ "type":"object",
247
+ "properties":{"name":{"type":"string"},"address":{type:"string"}},
248
+ "additionalProperties":true
249
+ },
250
+ settings: {
251
+ primary_keys:["name"],
252
+ non_editable_fields:["mobile"],
253
+ single_record:false,
254
+ encrypted_fields:[]
255
+ },
256
+ }],
257
+ ["settings.encrypted_fields not defined in the schema",
258
+ {
259
+ name: "contact",
260
+ description: "This can be left blank",
261
+ schema: {
262
+ "type":"object",
263
+ "properties":{"name":{"type":"string"},"address":{type:"object"},"secret":{"type":"string"}},
264
+ "additionalProperties":true
265
+ },
266
+ settings: {
267
+ primary_keys:["name"],
268
+ non_editable_fields:["mobile"],
269
+ single_record:false,
270
+ encrypted_fields:["password"]
271
+ },
272
+ }],
273
+ ["settings.encrypted_fields is not a string",
274
+ {
275
+ name: "contact",
276
+ description: "This can be left blank",
277
+ schema: {
278
+ "type":"object",
279
+ "properties":{"name":{"type":"string"},"address":{type:"object"},"secret":{"type":"string"}},
280
+ "additionalProperties":true
281
+ },
282
+ settings: {
283
+ primary_keys:["name"],
284
+ non_editable_fields:["mobile"],
285
+ single_record:false,
286
+ encrypted_fields:["address"]
287
+ },
288
+ }],
289
+ ["settings.encrypted_fields is a primary key",
290
+ {
291
+ name: "contact",
292
+ description: "This can be left blank",
293
+ schema: {
294
+ "type":"object",
295
+ "properties":{"name":{"type":"string"},"address":{type:"object"},"secret":{"type":"string"}},
296
+ "additionalProperties":true
297
+ },
298
+ settings: {
299
+ primary_keys:["name"],
300
+ non_editable_fields:["mobile"],
301
+ single_record:false,
302
+ encrypted_fields:["name"]
303
+ },
304
+ }]
123
305
  ];
124
306
 
125
- let doc_obj = get_pdb_doc("test_database_25", "qwertyuiopaqwsde1254");
126
- let database = new BeanBagDB(doc_obj);
127
- database.initialize_db();
128
- database.ready();
129
- // schema_docs_invalid.forEach(doc=>{})
130
- for (let index = 0; index < schema_docs_invalid.length; index++) {
131
- const element = schema_docs_invalid[index];
132
307
 
133
- it("throws error", async () => {
308
+ before(async () => {
309
+ let doc_obj = get_pdb_doc("test_database_25", "qwertyuiopaqwsde1254");
310
+ database = new BeanBagDB(doc_obj);
311
+ await database.ready(); // Ensure the database is ready before running tests
312
+ console.log("Ready for more tests...");
313
+ });
314
+
315
+ schema_docs_invalid.forEach((element, index) => {
316
+ it(`${element[0]}`, async () => {
134
317
  await rejects(
135
318
  async () => {
136
- await database.insert("schema", element);
319
+ await database.insert("schema", element[1]);
137
320
  },
138
- Error
321
+ ValidationError
139
322
  );
140
323
  });
141
-
142
- }
324
+ });
143
325
 
144
326
  });
145
327
 
package/test/pouchdb.js CHANGED
@@ -10,6 +10,7 @@ export const get_pdb_doc = (dbname,secret)=>{
10
10
  const pdb = new PouchDB(dbname);
11
11
  const doc_obj = {
12
12
  name: dbname,
13
+ db_name:"pouchdb",
13
14
  encryption_key: secret,
14
15
  api: {
15
16
  insert: async (doc) => {
package/test/test1.js CHANGED
@@ -158,21 +158,29 @@
158
158
 
159
159
  import { get_pdb_doc } from './pouchdb.js';
160
160
  import { throws, strictEqual } from "assert";
161
- import BeanBagDB from '../src/index.js';
161
+ import {BeanBagDB} from '../src/index.js';
162
162
 
163
163
  (async()=>{
164
+
164
165
  let schema_docs_invalid = [
165
166
  {
166
- "name":"",
167
- "description":"",
168
- "schema":{},
169
- "settings":{}
167
+ name: "contact",
168
+ description: "This can be left blank",
169
+ schema: {
170
+ "type":"object",
171
+ "properties":{"name":{"type":"string"},"address":{type:"object"},"secret":{"type":"string"}},
172
+ "additionalProperties":true
173
+ },
174
+ settings: {
175
+ primary_keys:["name"],
176
+ non_editable_fields:["address"],
177
+ single_record:false,
178
+ encrypted_fields:["name"]
179
+ },
170
180
  }
171
-
172
181
  ]
173
182
  let doc_obj = get_pdb_doc("test_database_26","qwertyuiopaqwsde1254")
174
183
  let database = new BeanBagDB(doc_obj);
175
- database.initialize_db()
176
- database.ready()
184
+ await database.ready()
177
185
  let a = await database.insert("schema",schema_docs_invalid[0])
178
186
  })()