beanbagdb 0.5.43 → 0.5.45

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,14 +1,12 @@
1
1
  {
2
2
  "name": "beanbagdb",
3
- "version": "0.5.43",
3
+ "version": "0.5.45",
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",
5
+ "main": "src/index.js",
6
+ "module": "src/index.js",
7
7
  "type": "module",
8
8
  "scripts": {
9
- "test": "mocha",
10
- "build": "rollup -c",
11
- "prepublishOnly": "npm run build"
9
+ "test": "mocha"
12
10
  },
13
11
  "repository": {
14
12
  "type": "git",
@@ -27,25 +25,13 @@
27
25
  "url": "https://github.com/shubhvjain/beanbagdb/issues"
28
26
  },
29
27
  "homepage": "https://github.com/shubhvjain/beanbagdb#readme",
30
- "dependencies": {
31
- "ajv": "^8.12.0",
32
- "dotenv": "^16.4.5",
33
- "nano": "^10.1.3",
34
- "pouchdb": "^9.0.0",
35
- "pouchdb-adapter-memory": "^9.0.0",
36
- "pouchdb-find": "^9.0.0"
37
- },
38
28
  "devDependencies": {
39
- "@rollup/plugin-commonjs": "^26.0.1",
40
- "@rollup/plugin-json": "^6.1.0",
41
- "@rollup/plugin-node-resolve": "^15.2.3",
42
- "@rollup/plugin-replace": "^5.0.7",
43
- "@rollup/plugin-terser": "^0.4.4",
29
+ "ajv": "^8.17.1",
44
30
  "chai": "^5.1.1",
31
+ "dotenv": "^16.4.5",
45
32
  "mocha": "^10.7.3",
46
- "rollup": "^4.21.2",
47
- "rollup-plugin-commonjs": "^10.1.0",
48
- "rollup-plugin-node-resolve": "^5.2.0"
49
- },
50
- "files": ["dist"]
33
+ "nano": "^10.1.4",
34
+ "pouchdb": "^9.0.0",
35
+ "pouchdb-find": "^9.0.0"
36
+ }
51
37
  }
package/src/index.js ADDED
@@ -0,0 +1,579 @@
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
+ /**
162
+ * Returns a document with the provided ID
163
+ * @param {String} doc_id - the doc Id (not the primary key)
164
+ * @param {Boolean} include_schema - whether to include the schema doc as well
165
+ * @returns {Object} {doc} or {doc,schema}
166
+ */
167
+ async get(doc_id,include_schema=false) {
168
+ let doc = await this.db_api.get(doc_id);
169
+ let schema = await this.get_schema_doc(doc.schema);
170
+ doc = this._decrypt_doc(schema, doc);
171
+ if(include_schema){
172
+ return {doc,schema}
173
+ }
174
+ return {doc};
175
+ }
176
+
177
+ /**
178
+ * Returns schema document for the given schema name s
179
+ * @param {String} schema_name - Schema name
180
+ */
181
+ async get_schema_doc(schema_name) {
182
+ let schemaSearch = await this.db_api.search({
183
+ selector: { schema: "schema", "data.name": schema_name },
184
+ });
185
+ if (schemaSearch.docs.length == 0) {
186
+ throw new Error("Schema not found");
187
+ }
188
+ return schemaSearch.docs[0]["data"];
189
+ }
190
+
191
+ /**
192
+ * Fetches a document based on a given schema and primary key.
193
+ * In case schema has a single record, leave the primary_key blank `[]`
194
+ * Can also be used to get special system docs such as settings
195
+ * @param {String} schema_name
196
+ * @param {Object} primary_key
197
+ * @returns object
198
+ */
199
+ async get_doc(schema_name, primary_key = {}) {
200
+ let s_doc = await this.get_schema_doc(schema_name);
201
+ let doc_obj;
202
+ if (
203
+ s_doc["settings"]["primary_keys"] &&
204
+ s_doc["settings"]["primary_keys"].length > 0
205
+ ) {
206
+ let A = s_doc["settings"]["primary_keys"];
207
+ let search_criteria = { schema: schema_name };
208
+ A.forEach((itm) => {
209
+ if (!primary_key[itm]) {
210
+ throw new Error(
211
+ "Incomplete Primary key set. Required field(s) : " + A.join(",")
212
+ );
213
+ }
214
+ search_criteria["data." + itm] = primary_key[itm];
215
+ });
216
+ let s = await this.search({ selector: search_criteria });
217
+ doc_obj = s.docs[0];
218
+ } else {
219
+ let s = await this.search({ selector: { schema: schema_name } });
220
+ if (s.docs.length > 1) {
221
+ throw new Error(
222
+ "Invalid schema. At least one primary key must be defined or set the singleRecord option to true. "
223
+ );
224
+ }
225
+ doc_obj = s.docs[0];
226
+ }
227
+ doc_obj = this._decrypt_doc(s_doc, doc_obj);
228
+ return doc_obj;
229
+ }
230
+
231
+ /**
232
+ * Searches for documents in the database for the specified query. The query are Mango queries.
233
+ * One field is mandatory : Schema
234
+ * E.g
235
+ * @param {Object} criteria
236
+ */
237
+ async search(criteria) {
238
+ if (!criteria["selector"]) {
239
+ throw new Error("Invalid search query.");
240
+ }
241
+ if (!criteria["selector"]["schema"]) {
242
+ throw new Error("The search criteria must contain the schema");
243
+ }
244
+ const results = await this.db_api.search(criteria);
245
+ return results;
246
+ }
247
+
248
+ /**
249
+ * Inserts a doc for the given schema
250
+ * @param {String} schema e.g "contact"
251
+ * @param {Object} data e.g {"name":"","mobile":""...}
252
+ * @param {Object} settings (optional)
253
+ */
254
+ async insert(schema, data, settings = {}) {
255
+ try {
256
+ let doc_obj = await this._insert_pre_checks(schema, data, settings);
257
+ let new_rec = await this.db_api.insert(doc_obj);
258
+ return { id: new_rec["id"] };
259
+ } catch (error) {
260
+ console.log(error);
261
+ throw error;
262
+ }
263
+ }
264
+
265
+
266
+
267
+
268
+ //** Update data */
269
+ /**
270
+ * Update data and meta of a doc.
271
+ *
272
+ * - Q: Which data fields can be edited ?
273
+ * - A: Depends on the setting.editable_fields. If this is blank, then all fields are editable.
274
+ * - Q: Are primary key fields editable ?
275
+ * - A: Yes. before making the update, a check is done to ensue the primary key policy is not violated
276
+ *
277
+ * @param {String} doc_id
278
+ * @param {String} rev_id
279
+ * @param {*} schema_name
280
+ * @param {doc_obj} updates {data:{},meta:{}}, need not be the full document, just the new values of all/some fields
281
+ * @param {Boolean} save_conflict = true -
282
+ * @returns
283
+ */
284
+ async update(doc_id, rev_id, updates, update_source="api",save_conflict = true) {
285
+ // making a big assumption here : primary key fields cannot be edited
286
+ // so updating the doc will not generate primary key conflicts
287
+ let req_data = await this.get(doc_id,true);
288
+ let schema = req_data.schema // await this.get_schema_doc(schema_name);
289
+ let full_doc = req_data.doc // await this.get(doc_id)["doc"];
290
+
291
+ // @TODO fix this : what to do if the rev id does not match
292
+ // if (full_doc["_rev"] != rev_id) {
293
+ // // throw error , save conflicting doc separately by default
294
+ // if (save_conflict) {
295
+ // // save conflicting doc todo
296
+ // }
297
+ // }
298
+
299
+ // blank check
300
+
301
+ // update new value depending on settings.editable_fields (if does not exists, all fields are editable)
302
+ let edit_fields = Object.keys(schema.schema.properties)
303
+ if(schema.settings["editable_fields"]&&schema.settings["editable_fields"].length>0){
304
+ edit_fields = schema.settings["editable_fields"]
305
+ }
306
+
307
+ // now generate the new doc with updates
308
+ let allowed_updates = this._filterObject(updates.data,edit_fields);
309
+ let updated_data = { ...full_doc.data, ...allowed_updates };
310
+
311
+ // validate data
312
+ this.validate_data(schema.schema, updated_data);
313
+
314
+ // primary key check if multiple records can be created
315
+ if(schema.settings["single_record"]==false){
316
+ if(schema.settings["primary_keys"]&&schema.settings["primary_keys"].length>0){
317
+ let pri_fields = schema.settings["primary_keys"]
318
+ let search_criteria = {schema:schema.name}
319
+ pri_fields.map(itm=>{search_criteria["data."+itm] = updated_data[itm]})
320
+ let search = await this.search({selection:search_criteria})
321
+ if(search.docs.length>0){
322
+ if(search.docs.length==1){
323
+ let thedoc = search.docs[0]
324
+ if(thedoc["_id"]!=doc_id){
325
+ throw new Error("Update not allowed. Document with the same primary key already exists")
326
+ }
327
+ }else{
328
+ throw new Error("There is something wrong with the schema")
329
+ }
330
+ }
331
+ }
332
+ }
333
+
334
+ // encrypt the data
335
+
336
+ full_doc["data"] = updated_data
337
+ full_doc = this._encrypt_doc(schema,full_doc);
338
+
339
+ if(updates.meta){
340
+ let m_sch = sys_sch.editable_metadata_schema
341
+ let editable_fields = Object.keys(m_sch["properties"])
342
+ let allowed_meta = this._filterObject(updates.meta,editable_fields)
343
+ this.validate_data(m_sch,allowed_meta)
344
+ full_doc["meta"] = {...full_doc["meta"],...allowed_meta}
345
+ }
346
+
347
+ full_doc.meta["updated_on"] = this._get_now_unix_timestamp()
348
+ full_doc.meta["updated_by"] = update_source
349
+ let up = await this.db_api.update(full_doc);
350
+ return up;
351
+ }
352
+
353
+ async delete(doc_id) {
354
+ await this.db_api.delete(doc_id)
355
+ }
356
+
357
+
358
+ async load_plugin(plugin_name,plugin_module){
359
+ this.plugins[plugin_name] = {}
360
+ for (let func_name in plugin_module){
361
+ if(typeof plugin_module[func_name]=='function'){
362
+ this.plugins[plugin_name][func_name] = plugin_module[func_name].bind(null,this)
363
+ }
364
+ }
365
+ // Check if the plugin has an on_load method and call it
366
+ if (typeof this.plugins[plugin_name].on_load === 'function') {
367
+ await this.plugins[plugin_name].on_load();
368
+ }
369
+ }
370
+
371
+ //////// Helper method ////////
372
+
373
+ _check_required_fields(requiredFields,obj){
374
+ for (const field of requiredFields) {
375
+ if (!obj[field]) {throw new Error(`${field} is required`);}
376
+ }
377
+ }
378
+
379
+ /**
380
+ *
381
+ * @param {*} obj
382
+ * @param {*} fields
383
+ * @returns
384
+ */
385
+ _filterObject(obj, fields) {
386
+ return fields.reduce((filteredObj, field) => {
387
+ if (Object.prototype.hasOwnProperty.call(obj, field)) {
388
+ filteredObj[field] = obj[field];
389
+ }
390
+ return filteredObj;
391
+ }, {});
392
+ }
393
+
394
+ /**
395
+ * Checks if the selected database is initialized for working with BeanBagDB. Also throws a warning if package version does not match with database version.
396
+ * 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.
397
+ * @returns {object} {initialized:boolean,latest:boolean}
398
+ */
399
+ async _check_ready_to_use() {
400
+ // @TODO check if ready to use in major API methods
401
+ let check = { initialized: false, latest: false };
402
+ // @TODO this is not really fool proof. check all the required docs, they have the system_generated flag
403
+ // what if some user mistakenly modifies or deletes some of the required docs ?
404
+ let version_search = await this.db_api.search({
405
+ selector: { schema: "system_settings", "data.name": "beanbagdb_version" },
406
+ });
407
+ if (version_search.docs.length > 0) {
408
+ let doc = version_search.docs[0];
409
+ check.initialized = true;
410
+ check.latest = doc["data"]["value"] == this._version;
411
+ }
412
+ if (check.initialized == false) {
413
+ console.warn(
414
+ "This database is not ready to be used. It is not initialized. Run `initialize_db()` first"
415
+ );
416
+ }
417
+ if ((check.latest == false) & (check.initialized == true)) {
418
+ console.warn(
419
+ "This database is not updated with the latest version. Run `initialize_db()` again to update to the latest version"
420
+ );
421
+ }
422
+ return check;
423
+ }
424
+
425
+ /**
426
+ * To update the system schema or reset to a stable version to ensure functioning of the BeanBagDB
427
+ */
428
+ async _update_system_schema() {
429
+ console.log("Todo");
430
+ }
431
+
432
+ /**
433
+ * Returns the current Unix timestamp in seconds.
434
+ * divide by 1000 (Date.now gives ms) to convert to seconds. 1 s = 1000 ms
435
+ * @returns {number}
436
+ */
437
+ _get_now_unix_timestamp() {
438
+ return Math.floor(Date.now() / 1000);
439
+ }
440
+
441
+ /**
442
+ * Generates a blank database json object. All objects in the database follow the same structure
443
+ * @param {string} schema_name
444
+ * @returns {object}
445
+ */
446
+ _get_blank_doc(schema_name) {
447
+ if (!schema_name) {
448
+ throw new Error("Schema name not provided for the blank doc");
449
+ }
450
+ let doc = {
451
+ data: {},
452
+ meta: {
453
+ createdOn: this._get_now_unix_timestamp(),
454
+ tags: [],
455
+ app :{}
456
+ },
457
+ schema: schema_name,
458
+ };
459
+ return doc;
460
+ }
461
+
462
+ /**
463
+ * Generates a blank schema doc ready to be inserted to the database. Note that no validation is done. This is for internal use
464
+ * @param {string} schema_name
465
+ * @param {Object} schema_object
466
+ * @param {Object} data
467
+ * @returns {Object}
468
+ */
469
+ _get_blank_schema_doc(schema_name, schema_object, data) {
470
+ this.validate_data(schema_object, data);
471
+ let obj = this._get_blank_doc(schema_name);
472
+ obj["data"] = data;
473
+ return obj;
474
+ }
475
+
476
+ /**
477
+ * Decrypts a given document using it's schema. The list of encrypted fields : schema_obj.settings.encrypted_fields
478
+ * @param {Object} schema_obj
479
+ * @param {Object} doc_obj
480
+ * @returns {Object}
481
+ */
482
+ _decrypt_doc(schema_obj, doc_obj) {
483
+ if (
484
+ schema_obj.settings["encrypted_fields"] &&
485
+ schema_obj.settings["encrypted_fields"].length > 0
486
+ ) {
487
+ schema_obj.settings["encrypted_fields"].forEach((itm) => {
488
+ doc_obj.data[itm] = this.utils.decrypt(
489
+ doc_obj.data[itm],
490
+ this.encryption_key
491
+ );
492
+ });
493
+ }
494
+ return { ...doc_obj };
495
+ }
496
+
497
+ /**
498
+ * Encrypts a given doc using it's schema obj.
499
+ * @param {Object} schema_obj
500
+ * @param {Object} doc_obj
501
+ * @returns {Object}
502
+ */
503
+ _encrypt_doc(schema_obj, doc_obj) {
504
+
505
+ if (
506
+ schema_obj.settings["encrypted_fields"] &&
507
+ schema_obj.settings["encrypted_fields"].length > 0
508
+ ) {
509
+ // console.log(schema_obj,doc_obj)
510
+ schema_obj.settings["encrypted_fields"].forEach((itm) => {
511
+ doc_obj.data[itm] = this.utils.encrypt(
512
+ doc_obj.data[itm],
513
+ this.encryption_key
514
+ );
515
+ });
516
+ }
517
+ return { ...doc_obj };
518
+ }
519
+
520
+ /**
521
+ * Checks if the new document is valid and ready to be inserted in the DB.
522
+ * List of checks:
523
+ * - fetch the schema object and validate the data object against the schema
524
+ * - check if the doc with same primary keys already exists
525
+ * - replace encrypted fields with encrypted values
526
+ * - return the doc
527
+ * @param {Object} schema
528
+ * @param {Object} data
529
+ */
530
+ async _insert_pre_checks(schema, data, settings = {}) {
531
+ // schema search
532
+ let sch_search = await this.search({
533
+ selector: { schema: "schema", "data.name": schema },
534
+ });
535
+ if (sch_search.docs.length == 0) {
536
+ throw new Error("Invalid Schema");
537
+ }
538
+ let schemaDoc = sch_search.docs[0]["data"];
539
+ // validate data
540
+ this.validate_data(schemaDoc.schema, data);
541
+
542
+ // special checks for special docs
543
+ // @TODO : for schema dos: settings fields must be in schema field
544
+ // @TODO : check if single record setting is set to true
545
+
546
+ // duplicate check
547
+ if (
548
+ schemaDoc.settings["primary_keys"] &&
549
+ schemaDoc.settings["primary_keys"].length > 0
550
+ ) {
551
+ let primary_obj = { schema: schema };
552
+ schemaDoc.settings["primary_keys"].map((ky) => {
553
+ primary_obj["data." + ky] = data[ky];
554
+ });
555
+ console.log(primary_obj);
556
+ let prim_search = await this.search({ selector: primary_obj });
557
+ console.log(prim_search);
558
+ if (prim_search.docs.length > 0) {
559
+ throw new Error("Doc already exists");
560
+ }
561
+ }
562
+ // encrypt if required
563
+ let new_data = { ...data };
564
+ if (
565
+ schemaDoc.settings["encrypted_fields"] &&
566
+ schemaDoc.settings["encrypted_fields"].length > 0
567
+ ) {
568
+ schemaDoc.settings["encrypted_fields"].forEach((itm) => {
569
+ new_data[itm] = this.utils.encrypt(data[itm], this.encryption_key);
570
+ });
571
+ }
572
+ // generate the doc object for data
573
+ let doc_obj = this._get_blank_doc(schema);
574
+ doc_obj["data"] = new_data;
575
+ return doc_obj;
576
+ }
577
+ }
578
+
579
+ export default BeanBagDB;