beanbagdb 0.5.44 → 0.5.46

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.
@@ -0,0 +1,41 @@
1
+ name: Release npm Package
2
+
3
+ # Trigger the workflow on push to the "main" branch
4
+ on:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: read
14
+ id-token: write
15
+ steps:
16
+ # Step 1: Check out the code from the repository
17
+ - name: Checkout code
18
+ uses: actions/checkout@v4
19
+
20
+ # Step 2: Set up Node.js environment
21
+ - name: Setup Node.js
22
+ uses: actions/setup-node@v4
23
+ with:
24
+ node-version: '20.17.0'
25
+ cache: 'npm'
26
+ registry-url: 'https://registry.npmjs.org'
27
+
28
+ # Step 3: Install dependencies
29
+ - name: Install dependencies
30
+ run: npm install
31
+
32
+ # Step 4: Run tests
33
+ # - name: Run tests
34
+ # run: npm test
35
+
36
+ # Step 5: Publish to npm if tests pass
37
+ - name: Publish to npm
38
+ run: npm publish --access public
39
+ env:
40
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
41
+
package/docs/logo.png ADDED
Binary file
package/package.json CHANGED
@@ -1,15 +1,12 @@
1
1
  {
2
2
  "name": "beanbagdb",
3
- "version": "0.5.44",
3
+ "version": "0.5.46",
4
4
  "description": "A JS library to introduce a schema layer to a No-SQL local database",
5
- "main": "dist/bundle.node.js",
6
- "module": "dist/bundle.browser.js",
7
- "browser": "dist/bundle.browser.js",
5
+ "main": "src/index.js",
6
+ "module": "src/index.js",
8
7
  "type": "module",
9
8
  "scripts": {
10
- "test": "mocha",
11
- "build": "rollup -c",
12
- "prepublishOnly": "npm run build"
9
+ "test": "mocha"
13
10
  },
14
11
  "repository": {
15
12
  "type": "git",
@@ -28,25 +25,13 @@
28
25
  "url": "https://github.com/shubhvjain/beanbagdb/issues"
29
26
  },
30
27
  "homepage": "https://github.com/shubhvjain/beanbagdb#readme",
31
- "dependencies": {
32
- "ajv": "^8.12.0",
33
- "dotenv": "^16.4.5",
34
- "nano": "^10.1.3",
35
- "pouchdb": "^9.0.0",
36
- "pouchdb-adapter-memory": "^9.0.0",
37
- "pouchdb-find": "^9.0.0"
38
- },
39
28
  "devDependencies": {
40
- "@rollup/plugin-commonjs": "^26.0.1",
41
- "@rollup/plugin-json": "^6.1.0",
42
- "@rollup/plugin-node-resolve": "^15.2.3",
43
- "@rollup/plugin-replace": "^5.0.7",
44
- "@rollup/plugin-terser": "^0.4.4",
29
+ "ajv": "^8.17.1",
45
30
  "chai": "^5.1.1",
31
+ "dotenv": "^16.4.5",
46
32
  "mocha": "^10.7.3",
47
- "rollup": "^4.21.2",
48
- "rollup-plugin-commonjs": "^10.1.0",
49
- "rollup-plugin-node-resolve": "^5.2.0"
50
- },
51
- "files": ["dist"]
33
+ "nano": "^10.1.4",
34
+ "pouchdb": "^9.0.0",
35
+ "pouchdb-find": "^9.0.0"
36
+ }
52
37
  }
package/src/index.js ADDED
@@ -0,0 +1,601 @@
1
+ import * as sys_sch from "./system_schema.js";
2
+ // import { version } from "../package.json" assert {type :"json"};
3
+ /**
4
+ * 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
+ * It takes a db_instance argument, which , this class relies on perform CRUD operations on the data.
6
+ * Why have a "dumb" class ? : So that the core functionalities remains in a single place and the multiple Databases can be supported.
7
+ */
8
+ class BeanBagDB {
9
+ /**
10
+ * @param {object} db_instance - Database object
11
+ * db_instance object contains 3 main keys :
12
+ * - `name` : the name of the local database
13
+ * - `encryption_key`: this is required for encrypting documents
14
+ * - `api` : this is an object that must contain database specific functions. This includes : `insert(doc)`: takes a doc and runs the db insertion function, `update(updated_doc)` : gets the updated document and updates it in the DB, `search(query)`: takes a query to fetch data from the DB (assuming array of JSON is returned ), `get(id)`: takes a document id and returns its content, `createIndex(filter)`: to create an index in the database based on a filter
15
+ * - `utils` : this includes `encrypt`, `decrypt`
16
+ */
17
+ constructor(db_instance) {
18
+ // 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
+
23
+ if(db_instance.encryption_key.length>20){throw new Error("encryption_key must have at least 20 letters")}
24
+ // db name should not be blank,
25
+
26
+ this.name = db_instance.name;
27
+ this.encryption_key = db_instance.encryption_key;
28
+
29
+ this.db_api = db_instance.api;
30
+ this.utils = db_instance.utils;
31
+
32
+ this._version = "0.5.0"
33
+ this.ready_check = { initialized: false, latest: false };
34
+ console.log("Run ready() now");
35
+
36
+ this.plugins = {}
37
+ }
38
+
39
+ /**
40
+ * This is to check if the database is ready to be used. It it important to run this after the class is initialized.
41
+ */
42
+ 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!");
49
+ }
50
+ }
51
+
52
+ check_if_ready(){
53
+ return this.ready_check.ready
54
+ }
55
+
56
+ /**
57
+ * Initializes the database making it ready to be used. Typically, required to run after every time package is updated to a new version.
58
+ * See the documentation on the architecture of the DB to understand what default schemas are required for a smooth functioning of the database
59
+ */
60
+ async initialize_db() {
61
+ try {
62
+ if (this.ready_check.initialized == false) {
63
+ // add the meta-schemas doc
64
+ let schema_schema_doc = this._get_blank_doc("schema");
65
+ schema_schema_doc.data = sys_sch.schema_schema;
66
+ 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);
77
+ }
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,
104
+ }
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");
118
+ }
119
+ }
120
+ } catch (error) {
121
+ console.log(error);
122
+ throw error;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * 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
+ */
129
+ async update_indexes() {
130
+ // @TODO check this. i don't the index created this way are actually useful in search.
131
+ let all_schemas_docs = await this.db_api.search({
132
+ selector: { schema: "schema" },
133
+ });
134
+ let indexes = [];
135
+ all_schemas_docs.docs.map((item) => {
136
+ Object.keys(item.data.schema.properties).map((key) => {
137
+ indexes.push("data." + key);
138
+ });
139
+ });
140
+ await this.db_api.createIndex({ index: { fields: indexes } });
141
+ }
142
+
143
+ /**
144
+ * Validates a data object against a provided JSON schema
145
+ * It relies on the Ajv package to make the validation.
146
+ * @param {Object} schema_obj - The JSON schema object to validate against
147
+ * @param {Object} data_obj - The data object to validate
148
+ * @throws {Error} If the data object does not conform to the schema
149
+ */
150
+ validate_data(schema_obj, data_obj) {
151
+ const {valid,validate} = this.utils.validate_schema(schema_obj, data_obj)
152
+ //const ajv = new Ajv({code: {esm: true}}) // options can be passed, e.g. {allErrors: true}
153
+ //const validate = ajv.compile(schema_obj);
154
+ //const valid = validate(data_obj);
155
+ if (!valid) {
156
+ console.log(validate.errors);
157
+ throw new Error(validate.errors);
158
+ }
159
+ }
160
+
161
+ validate_schema_object(schema_doc){
162
+
163
+ }
164
+
165
+ /**
166
+ * Returns a document with the provided ID
167
+ * @param {String} doc_id - the doc Id (not the primary key)
168
+ * @param {Boolean} include_schema - whether to include the schema doc as well
169
+ * @returns {Object} {doc} or {doc,schema}
170
+ */
171
+ async get(doc_id,include_schema=false) {
172
+ let doc = await this.db_api.get(doc_id);
173
+ let schema = await this.get_schema_doc(doc.schema);
174
+ doc = this._decrypt_doc(schema, doc);
175
+ if(include_schema){
176
+ return {doc,schema}
177
+ }
178
+ return {doc};
179
+ }
180
+
181
+ /**
182
+ * Returns schema document for the given schema name s
183
+ * @param {String} schema_name - Schema name
184
+ */
185
+ async get_schema_doc(schema_name) {
186
+ let schemaSearch = await this.db_api.search({
187
+ selector: { schema: "schema", "data.name": schema_name },
188
+ });
189
+ if (schemaSearch.docs.length == 0) {
190
+ throw new Error("Schema not found");
191
+ }
192
+ return schemaSearch.docs[0]["data"];
193
+ }
194
+
195
+ /**
196
+ * Fetches a document based on a given schema and primary key.
197
+ * In case schema has a single record, leave the primary_key blank `[]`
198
+ * Can also be used to get special system docs such as settings
199
+ * @param {String} schema_name
200
+ * @param {Object} primary_key
201
+ * @returns object
202
+ */
203
+ async get_doc(schema_name, primary_key = {}) {
204
+ let s_doc = await this.get_schema_doc(schema_name);
205
+ let doc_obj;
206
+ if (
207
+ s_doc["settings"]["primary_keys"] &&
208
+ s_doc["settings"]["primary_keys"].length > 0
209
+ ) {
210
+ let A = s_doc["settings"]["primary_keys"];
211
+ let search_criteria = { schema: schema_name };
212
+ A.forEach((itm) => {
213
+ if (!primary_key[itm]) {
214
+ throw new Error(
215
+ "Incomplete Primary key set. Required field(s) : " + A.join(",")
216
+ );
217
+ }
218
+ search_criteria["data." + itm] = primary_key[itm];
219
+ });
220
+ let s = await this.search({ selector: search_criteria });
221
+ doc_obj = s.docs[0];
222
+ } else {
223
+ let s = await this.search({ selector: { schema: schema_name } });
224
+ if (s.docs.length > 1) {
225
+ throw new Error(
226
+ "Invalid schema. At least one primary key must be defined or set the singleRecord option to true. "
227
+ );
228
+ }
229
+ doc_obj = s.docs[0];
230
+ }
231
+ doc_obj = this._decrypt_doc(s_doc, doc_obj);
232
+ return doc_obj;
233
+ }
234
+
235
+ /**
236
+ * Searches for documents in the database for the specified query. The query are Mango queries.
237
+ * One field is mandatory : Schema
238
+ * E.g
239
+ * @param {Object} criteria
240
+ */
241
+ async search(criteria) {
242
+ if (!criteria["selector"]) {
243
+ throw new Error("Invalid search query.");
244
+ }
245
+ if (!criteria["selector"]["schema"]) {
246
+ throw new Error("The search criteria must contain the schema");
247
+ }
248
+ const results = await this.db_api.search(criteria);
249
+ return results;
250
+ }
251
+
252
+ /**
253
+ * Inserts a doc for the given schema
254
+ * @param {String} schema e.g "contact"
255
+ * @param {Object} data e.g {"name":"","mobile":""...}
256
+ * @param {Object} settings (optional)
257
+ */
258
+ async insert(schema, data, settings = {}) {
259
+ try {
260
+ let doc_obj = await this._insert_pre_checks(schema, data, settings);
261
+ let new_rec = await this.db_api.insert(doc_obj);
262
+ return { id: new_rec["id"] };
263
+ } catch (error) {
264
+ console.log(error);
265
+ throw error;
266
+ }
267
+ }
268
+
269
+
270
+
271
+
272
+ //** Update data */
273
+ /**
274
+ * Update data and meta of a doc.
275
+ *
276
+ * - Q: Which data fields can be edited ?
277
+ * - A: Depends on the setting.editable_fields. If this is blank, then all fields are editable.
278
+ * - Q: Are primary key fields editable ?
279
+ * - A: Yes. before making the update, a check is done to ensue the primary key policy is not violated
280
+ *
281
+ * @param {String} doc_id
282
+ * @param {String} rev_id
283
+ * @param {*} schema_name
284
+ * @param {doc_obj} updates {data:{},meta:{}}, need not be the full document, just the new values of all/some fields
285
+ * @param {Boolean} save_conflict = true -
286
+ * @returns
287
+ */
288
+ async update(doc_id, rev_id, updates, update_source="api",save_conflict = true) {
289
+ // making a big assumption here : primary key fields cannot be edited
290
+ // so updating the doc will not generate primary key conflicts
291
+ let req_data = await this.get(doc_id,true);
292
+ let schema = req_data.schema // await this.get_schema_doc(schema_name);
293
+ let full_doc = req_data.doc // await this.get(doc_id)["doc"];
294
+
295
+ // @TODO fix this : what to do if the rev id does not match
296
+ // if (full_doc["_rev"] != rev_id) {
297
+ // // throw error , save conflicting doc separately by default
298
+ // if (save_conflict) {
299
+ // // save conflicting doc todo
300
+ // }
301
+ // }
302
+
303
+ // blank check
304
+
305
+ // update new value depending on settings.editable_fields (if does not exists, all fields are editable)
306
+ let edit_fields = Object.keys(schema.schema.properties)
307
+ if(schema.settings["editable_fields"]&&schema.settings["editable_fields"].length>0){
308
+ edit_fields = schema.settings["editable_fields"]
309
+ }
310
+
311
+ // now generate the new doc with updates
312
+ let allowed_updates = this._filterObject(updates.data,edit_fields);
313
+ let updated_data = { ...full_doc.data, ...allowed_updates };
314
+
315
+ // validate data
316
+ this.validate_data(schema.schema, updated_data);
317
+
318
+ // primary key check if multiple records can be created
319
+ if(schema.settings["single_record"]==false){
320
+ if(schema.settings["primary_keys"]&&schema.settings["primary_keys"].length>0){
321
+ let pri_fields = schema.settings["primary_keys"]
322
+ let search_criteria = {schema:schema.name}
323
+ pri_fields.map(itm=>{search_criteria["data."+itm] = updated_data[itm]})
324
+ let search = await this.search({selection:search_criteria})
325
+ if(search.docs.length>0){
326
+ if(search.docs.length==1){
327
+ let thedoc = search.docs[0]
328
+ if(thedoc["_id"]!=doc_id){
329
+ throw new Error("Update not allowed. Document with the same primary key already exists")
330
+ }
331
+ }else{
332
+ throw new Error("There is something wrong with the schema")
333
+ }
334
+ }
335
+ }
336
+ }
337
+
338
+ // encrypt the data
339
+
340
+ full_doc["data"] = updated_data
341
+ full_doc = this._encrypt_doc(schema,full_doc);
342
+
343
+ if(updates.meta){
344
+ let m_sch = sys_sch.editable_metadata_schema
345
+ let editable_fields = Object.keys(m_sch["properties"])
346
+ let allowed_meta = this._filterObject(updates.meta,editable_fields)
347
+ this.validate_data(m_sch,allowed_meta)
348
+ full_doc["meta"] = {...full_doc["meta"],...allowed_meta}
349
+ }
350
+
351
+ full_doc.meta["updated_on"] = this._get_now_unix_timestamp()
352
+ full_doc.meta["updated_by"] = update_source
353
+ let up = await this.db_api.update(full_doc);
354
+ return up;
355
+ }
356
+
357
+ async delete(doc_id) {
358
+ await this.db_api.delete(doc_id)
359
+ }
360
+
361
+
362
+ async load_plugin(plugin_name,plugin_module){
363
+ this.plugins[plugin_name] = {}
364
+ for (let func_name in plugin_module){
365
+ if(typeof plugin_module[func_name]=='function'){
366
+ this.plugins[plugin_name][func_name] = plugin_module[func_name].bind(null,this)
367
+ }
368
+ }
369
+ // Check if the plugin has an on_load method and call it
370
+ if (typeof this.plugins[plugin_name].on_load === 'function') {
371
+ await this.plugins[plugin_name].on_load();
372
+ }
373
+ }
374
+
375
+ //////// Helper method ////////
376
+
377
+ _generate_random_link(){
378
+ 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'];
379
+ return Array.from({ length: 4 }, () => dictionary[Math.floor(Math.random() * dictionary.length)]).join('-');
380
+ }
381
+
382
+ _check_required_fields(requiredFields,obj){
383
+ for (const field of requiredFields) {
384
+ if (!obj[field]) {throw new Error(`${field} is required`);}
385
+ }
386
+ }
387
+
388
+ /**
389
+ *
390
+ * @param {*} obj
391
+ * @param {*} fields
392
+ * @returns
393
+ */
394
+ _filterObject(obj, fields) {
395
+ return fields.reduce((filteredObj, field) => {
396
+ if (Object.prototype.hasOwnProperty.call(obj, field)) {
397
+ filteredObj[field] = obj[field];
398
+ }
399
+ return filteredObj;
400
+ }, {});
401
+ }
402
+
403
+ /**
404
+ * Checks if the selected database is initialized for working with BeanBagDB. Also throws a warning if package version does not match with database version.
405
+ * 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.
406
+ * @returns {object} {initialized:boolean,latest:boolean}
407
+ */
408
+ async _check_ready_to_use() {
409
+ // @TODO check if ready to use in major API methods
410
+ let check = { initialized: false, latest: false };
411
+ // @TODO this is not really fool proof. check all the required docs, they have the system_generated flag
412
+ // what if some user mistakenly modifies or deletes some of the required docs ?
413
+ let version_search = await this.db_api.search({
414
+ selector: { schema: "system_settings", "data.name": "beanbagdb_version" },
415
+ });
416
+ if (version_search.docs.length > 0) {
417
+ let doc = version_search.docs[0];
418
+ check.initialized = true;
419
+ check.latest = doc["data"]["value"] == this._version;
420
+ }
421
+ if (check.initialized == false) {
422
+ console.warn(
423
+ "This database is not ready to be used. It is not initialized. Run `initialize_db()` first"
424
+ );
425
+ }
426
+ if ((check.latest == false) & (check.initialized == true)) {
427
+ console.warn(
428
+ "This database is not updated with the latest version. Run `initialize_db()` again to update to the latest version"
429
+ );
430
+ }
431
+ return check;
432
+ }
433
+
434
+ /**
435
+ * To update the system schema or reset to a stable version to ensure functioning of the BeanBagDB
436
+ */
437
+ async _update_system_schema() {
438
+ console.log("Todo");
439
+ }
440
+
441
+ /**
442
+ * Returns the current Unix timestamp in seconds.
443
+ * divide by 1000 (Date.now gives ms) to convert to seconds. 1 s = 1000 ms
444
+ * @returns {number}
445
+ */
446
+ _get_now_unix_timestamp() {
447
+ return Math.floor(Date.now() / 1000);
448
+ }
449
+
450
+ /**
451
+ * Generates a blank database json object. All objects in the database follow the same structure
452
+ * @param {string} schema_name
453
+ * @returns {object}
454
+ */
455
+ _get_blank_doc(schema_name) {
456
+ if (!schema_name) {
457
+ throw new Error("Schema name not provided for the blank doc");
458
+ }
459
+ let doc = {
460
+ data: {},
461
+ meta: {
462
+ createdOn: this._get_now_unix_timestamp(),
463
+ tags: [],
464
+ app :{},
465
+ 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
466
+ },
467
+ schema: schema_name,
468
+ };
469
+ return doc;
470
+ }
471
+
472
+ /**
473
+ * Generates a blank schema doc ready to be inserted to the database. Note that no validation is done. This is for internal use
474
+ * @param {string} schema_name
475
+ * @param {Object} schema_object
476
+ * @param {Object} data
477
+ * @returns {Object}
478
+ */
479
+ _get_blank_schema_doc(schema_name, schema_object, data) {
480
+ this.validate_data(schema_object, data);
481
+ let obj = this._get_blank_doc(schema_name);
482
+ obj["data"] = data;
483
+ return obj;
484
+ }
485
+
486
+ /**
487
+ * Decrypts a given document using it's schema. The list of encrypted fields : schema_obj.settings.encrypted_fields
488
+ * @param {Object} schema_obj
489
+ * @param {Object} doc_obj
490
+ * @returns {Object}
491
+ */
492
+ _decrypt_doc(schema_obj, doc_obj) {
493
+ if (
494
+ schema_obj.settings["encrypted_fields"] &&
495
+ schema_obj.settings["encrypted_fields"].length > 0
496
+ ) {
497
+ schema_obj.settings["encrypted_fields"].forEach((itm) => {
498
+ doc_obj.data[itm] = this.utils.decrypt(
499
+ doc_obj.data[itm],
500
+ this.encryption_key
501
+ );
502
+ });
503
+ }
504
+ return { ...doc_obj };
505
+ }
506
+
507
+ /**
508
+ * Encrypts a given doc using it's schema obj.
509
+ * @param {Object} schema_obj
510
+ * @param {Object} doc_obj
511
+ * @returns {Object}
512
+ */
513
+ _encrypt_doc(schema_obj, doc_obj) {
514
+
515
+ if (
516
+ schema_obj.settings["encrypted_fields"] &&
517
+ schema_obj.settings["encrypted_fields"].length > 0
518
+ ) {
519
+ // console.log(schema_obj,doc_obj)
520
+ schema_obj.settings["encrypted_fields"].forEach((itm) => {
521
+ doc_obj.data[itm] = this.utils.encrypt(
522
+ doc_obj.data[itm],
523
+ this.encryption_key
524
+ );
525
+ });
526
+ }
527
+ return { ...doc_obj };
528
+ }
529
+
530
+ /**
531
+ * Checks if the new document is valid and ready to be inserted in the DB.
532
+ * List of checks:
533
+ * - fetch the schema object and validate the data object against the schema
534
+ * - check if the doc with same primary keys already exists
535
+ * - replace encrypted fields with encrypted values
536
+ * - return the doc
537
+ * @param {Object} schema
538
+ * @param {Object} data
539
+ */
540
+ async _insert_pre_checks(schema, data,meta={} ,settings = {}) {
541
+ // schema search
542
+ let sch_search = await this.search({
543
+ selector: { schema: "schema", "data.name": schema },
544
+ });
545
+ if (sch_search.docs.length == 0) {
546
+ throw new Error("Invalid Schema");
547
+ }
548
+ let schemaDoc = sch_search.docs[0]["data"];
549
+ // validate data
550
+ this.validate_data(schemaDoc.schema, data);
551
+
552
+ // validate meta
553
+ this.validate_data(sys_sch.editable_metadata_schema, meta);
554
+
555
+ // duplicate meta.link check
556
+ if(meta.link){
557
+ let link_search = await this.search({ selector: {"meta.link":meta.link} });
558
+ console.log(link_search);
559
+ if (link_search.docs.length > 0) {
560
+ throw new Error("This link already exists in the database");
561
+ }
562
+ }
563
+
564
+ // special checks for special docs
565
+ // @TODO : for schema dos: settings fields must be in schema field
566
+ // @TODO : check if single record setting is set to true
567
+
568
+ // duplicate check
569
+ if (
570
+ schemaDoc.settings["primary_keys"] &&
571
+ schemaDoc.settings["primary_keys"].length > 0
572
+ ) {
573
+ let primary_obj = { schema: schema };
574
+ schemaDoc.settings["primary_keys"].map((ky) => {
575
+ primary_obj["data." + ky] = data[ky];
576
+ });
577
+ console.log(primary_obj);
578
+ let prim_search = await this.search({ selector: primary_obj });
579
+ console.log(prim_search);
580
+ if (prim_search.docs.length > 0) {
581
+ throw new Error("Doc already exists");
582
+ }
583
+ }
584
+ // encrypt if required
585
+ let new_data = { ...data };
586
+ if (
587
+ schemaDoc.settings["encrypted_fields"] &&
588
+ schemaDoc.settings["encrypted_fields"].length > 0
589
+ ) {
590
+ schemaDoc.settings["encrypted_fields"].forEach((itm) => {
591
+ new_data[itm] = this.utils.encrypt(data[itm], this.encryption_key);
592
+ });
593
+ }
594
+ // generate the doc object for data
595
+ let doc_obj = this._get_blank_doc(schema);
596
+ doc_obj["data"] = new_data;
597
+ return doc_obj;
598
+ }
599
+ }
600
+
601
+ export default BeanBagDB;