masterrecord 0.2.36 → 0.3.1

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.
Files changed (38) hide show
  1. package/.claude/settings.local.json +20 -1
  2. package/Entity/entityModel.js +6 -0
  3. package/Entity/entityTrackerModel.js +20 -3
  4. package/Entity/fieldTransformer.js +266 -0
  5. package/Migrations/migrationMySQLQuery.js +145 -1
  6. package/Migrations/migrationPostgresQuery.js +402 -0
  7. package/Migrations/migrationSQLiteQuery.js +145 -1
  8. package/Migrations/schema.js +131 -28
  9. package/QueryLanguage/queryMethods.js +193 -15
  10. package/QueryLanguage/queryParameters.js +136 -0
  11. package/QueryLanguage/queryScript.js +13 -4
  12. package/SQLLiteEngine.js +331 -20
  13. package/context.js +91 -14
  14. package/docs/INCLUDES_CLARIFICATION.md +202 -0
  15. package/docs/METHODS_REFERENCE.md +184 -0
  16. package/docs/MIGRATIONS_GUIDE.md +699 -0
  17. package/docs/POSTGRESQL_SETUP.md +415 -0
  18. package/examples/jsonArrayTransformer.js +215 -0
  19. package/mySQLEngine.js +273 -17
  20. package/package.json +3 -3
  21. package/postgresEngine.js +600 -483
  22. package/postgresSyncConnect.js +209 -0
  23. package/readme.md +1046 -416
  24. package/test/anyCommaStringTest.js +237 -0
  25. package/test/anyMethodTest.js +176 -0
  26. package/test/findByIdTest.js +227 -0
  27. package/test/includesFeatureTest.js +183 -0
  28. package/test/includesTransformTest.js +110 -0
  29. package/test/newMethodTest.js +330 -0
  30. package/test/newMethodUnitTest.js +320 -0
  31. package/test/parameterizedPlaceholderTest.js +159 -0
  32. package/test/postgresEngineTest.js +463 -0
  33. package/test/postgresIntegrationTest.js +381 -0
  34. package/test/securityTest.js +268 -0
  35. package/test/singleDollarPlaceholderTest.js +238 -0
  36. package/test/transformerTest.js +287 -0
  37. package/test/verifyFindById.js +169 -0
  38. package/test/verifyNewMethod.js +191 -0
@@ -1,11 +1,16 @@
1
- // version 0.0.8
1
+ // version 0.0.9
2
2
 
3
3
  const LOG_OPERATORS_REGEX = /(\|\|)|(&&)/;
4
4
  var tools = require('../Tools');
5
+ const QueryParameters = require('./queryParameters');
5
6
 
6
7
  class queryScript{
7
8
 
8
- constructor(){ }
9
+ constructor(){
10
+ this.parameters = new QueryParameters();
11
+ // Initialize script.parameters reference
12
+ this.script.parameters = this.parameters;
13
+ }
9
14
 
10
15
  script = {
11
16
  select : false,
@@ -19,11 +24,13 @@ class queryScript{
19
24
  skip: 0,
20
25
  orderBy : false,
21
26
  orderByDesc : false,
22
- parentName : ""
27
+ parentName : "",
28
+ parameters: null // Will hold QueryParameters instance
23
29
  };
24
30
 
25
31
 
26
32
  reset(){
33
+ this.parameters.reset();
27
34
  this.script = {
28
35
  select : false,
29
36
  where: false,
@@ -35,7 +42,9 @@ class queryScript{
35
42
  take : 0,
36
43
  skip: 0,
37
44
  orderBy : false,
38
- orderByDesc : false
45
+ orderByDesc : false,
46
+ parentName : "",
47
+ parameters: this.parameters
39
48
  };
40
49
  }
41
50
 
package/SQLLiteEngine.js CHANGED
@@ -1,28 +1,46 @@
1
1
  // Version 0.0.23
2
2
  var tools = require('masterrecord/Tools');
3
+ var FieldTransformer = require('masterrecord/Entity/fieldTransformer');
3
4
 
4
5
  class SQLLiteEngine {
5
6
 
6
7
  unsupportedWords = ["order"]
7
8
 
8
9
  update(query){
9
- var sqlQuery = ` UPDATE [${query.tableName}]
10
- SET ${query.arg}
11
- WHERE [${query.tableName}].[${query.primaryKey}] = ${query.primaryKeyValue}` // primary key for that table =
12
- return this._run(sqlQuery);
10
+ // Use parameterized query for security
11
+ // query.arg now contains {sql, params} from _buildSQLEqualToParameterized
12
+ if(query.arg && typeof query.arg === 'object' && query.arg.sql && query.arg.params){
13
+ var sqlQuery = ` UPDATE [${query.tableName}]
14
+ SET ${query.arg.sql}
15
+ WHERE [${query.tableName}].[${query.primaryKey}] = ?`;
16
+ // Add primaryKeyValue to params array
17
+ var params = [...query.arg.params, query.primaryKeyValue];
18
+ return this._runWithParams(sqlQuery, params);
19
+ } else {
20
+ // Fallback to old method (for backwards compatibility during migration)
21
+ var sqlQuery = ` UPDATE [${query.tableName}]
22
+ SET ${query.arg}
23
+ WHERE [${query.tableName}].[${query.primaryKey}] = ?`;
24
+ return this._runWithParams(sqlQuery, [query.primaryKeyValue]);
25
+ }
13
26
  }
14
27
 
15
28
  delete(queryObject){
16
29
  var sqlObject = this._buildDeleteObject(queryObject);
17
- var sqlQuery = `DELETE FROM [${sqlObject.tableName}] WHERE [${sqlObject.tableName}].[${sqlObject.primaryKey}] = ${sqlObject.value}`;
18
- return this._execute(sqlQuery);
30
+ // Use parameterized query to prevent SQL injection
31
+ var sqlQuery = `DELETE FROM [${sqlObject.tableName}] WHERE [${sqlObject.tableName}].[${sqlObject.primaryKey}] = ?`;
32
+ return this._executeWithParams(sqlQuery, [sqlObject.value]);
19
33
  }
20
34
 
21
35
  insert(queryObject){
22
- var sqlObject = this._buildSQLInsertObject(queryObject, queryObject.__entity);
36
+ // Use NEW SECURE parameterized version
37
+ var sqlObject = this._buildSQLInsertObjectParameterized(queryObject, queryObject.__entity);
38
+ if(sqlObject === -1){
39
+ throw new Error('INSERT failed: No columns to insert');
40
+ }
23
41
  var query = `INSERT INTO [${sqlObject.tableName}] (${sqlObject.columns})
24
- VALUES (${sqlObject.values})`;
25
- var queryObj = this._run(query);
42
+ VALUES (${sqlObject.placeholders})`;
43
+ var queryObj = this._runWithParams(query, sqlObject.params);
26
44
  var open = {
27
45
  "id": queryObj.lastInsertRowid
28
46
  };
@@ -44,8 +62,11 @@ class SQLLiteEngine {
44
62
  }
45
63
  }
46
64
  if(queryString.query){
65
+ // Get parameters from query script
66
+ const params = query.parameters ? query.parameters.getParams() : [];
47
67
  console.log("SQL:", queryString.query);
48
- var queryReturn = this.db.prepare(queryString.query).get();
68
+ console.log("Params:", params);
69
+ var queryReturn = this.db.prepare(queryString.query).get(...params);
49
70
  return queryReturn;
50
71
  }
51
72
  return null;
@@ -58,8 +79,9 @@ class SQLLiteEngine {
58
79
  // Introspection helpers
59
80
  tableExists(tableName){
60
81
  try{
61
- const sql = `SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`;
62
- const row = this.db.prepare(sql).get();
82
+ // Use parameterized query to prevent SQL injection
83
+ const sql = `SELECT name FROM sqlite_master WHERE type='table' AND name=?`;
84
+ const row = this.db.prepare(sql).get(tableName);
63
85
  return !!row;
64
86
  }catch(_){ return false; }
65
87
  }
@@ -88,8 +110,11 @@ class SQLLiteEngine {
88
110
  }
89
111
  if(queryString.query){
90
112
  var queryCount = queryString.query
113
+ // Get parameters from query script
114
+ const params = query.parameters ? query.parameters.getParams() : [];
91
115
  console.log("SQL:", queryCount );
92
- var queryReturn = this.db.prepare(queryCount ).get();
116
+ console.log("Params:", params);
117
+ var queryReturn = this.db.prepare(queryCount).get(...params);
93
118
  return queryReturn;
94
119
  }
95
120
  return null;
@@ -110,8 +135,11 @@ class SQLLiteEngine {
110
135
  selectQuery = this.buildQuery(query, entity, context);
111
136
  }
112
137
  if(selectQuery.query){
138
+ // Get parameters from query script
139
+ const params = query.parameters ? query.parameters.getParams() : [];
113
140
  console.log("SQL:", selectQuery.query);
114
- var queryReturn = this.db.prepare(selectQuery.query).all();
141
+ console.log("Params:", params);
142
+ var queryReturn = this.db.prepare(selectQuery.query).all(...params);
115
143
  return queryReturn;
116
144
  }
117
145
  return null;
@@ -247,16 +275,28 @@ class SQLLiteEngine {
247
275
  if(item.expressions[exp].arg === "null"){
248
276
  strQuery = `${entity}.${field} ${item.expressions[exp].func} ${item.expressions[exp].arg}`;
249
277
  }else{
250
- strQuery = `${entity}.${field} ${item.expressions[exp].func} '${item.expressions[exp].arg}'`;
278
+ // Check if arg is a parameterized placeholder
279
+ var isPlaceholder = (item.expressions[exp].arg === '?' || /^\$\d+$/.test(item.expressions[exp].arg));
280
+ if(isPlaceholder){
281
+ strQuery = `${entity}.${field} ${item.expressions[exp].func} ${item.expressions[exp].arg}`;
282
+ }else{
283
+ strQuery = `${entity}.${field} ${item.expressions[exp].func} '${item.expressions[exp].arg}'`;
284
+ }
251
285
  }
252
286
  }
253
287
  else{
254
288
  if(item.expressions[exp].arg === "null"){
255
289
  strQuery = `${strQuery} and ${entity}.${field} ${item.expressions[exp].func} ${item.expressions[exp].arg}`;
256
290
  }else{
257
- strQuery = `${strQuery} and ${entity}.${field} ${item.expressions[exp].func} '${item.expressions[exp].arg}'`;
291
+ // Check if arg is a parameterized placeholder
292
+ var isPlaceholder = (item.expressions[exp].arg === '?' || /^\$\d+$/.test(item.expressions[exp].arg));
293
+ if(isPlaceholder){
294
+ strQuery = `${strQuery} and ${entity}.${field} ${item.expressions[exp].func} ${item.expressions[exp].arg}`;
295
+ }else{
296
+ strQuery = `${strQuery} and ${entity}.${field} ${item.expressions[exp].func} '${item.expressions[exp].arg}'`;
297
+ }
258
298
  }
259
-
299
+
260
300
  }
261
301
  }
262
302
  andList.push(strQuery);
@@ -313,6 +353,12 @@ class SQLLiteEngine {
313
353
  if(func === "IN"){
314
354
  return `${ent}.${field} ${func} ${arg}`;
315
355
  }
356
+ // Check if arg is a parameterized placeholder (? for MySQL/SQLite, $1/$2/etc for Postgres)
357
+ var isPlaceholder = (arg === '?' || /^\$\d+$/.test(arg));
358
+ if(isPlaceholder){
359
+ // Don't quote placeholders - they must remain as bare ? or $1
360
+ return `${ent}.${field} ${func} ${arg}`;
361
+ }
316
362
  return `${ent}.${field} ${func} '${arg}'`;
317
363
  }
318
364
 
@@ -648,10 +694,169 @@ class SQLLiteEngine {
648
694
  else{
649
695
  return -1;
650
696
  }
651
-
697
+
652
698
  }
653
699
 
654
-
700
+ /**
701
+ * NEW SECURE VERSION: Build SQL SET clause with parameterized queries
702
+ * Returns {sql: "column1 = ?, column2 = ?", params: [value1, value2]}
703
+ * This prevents SQL injection by separating SQL structure from values
704
+ */
705
+ _buildSQLEqualToParameterized(model){
706
+ var $that = this;
707
+ var sqlParts = [];
708
+ var params = [];
709
+ var dirtyFields = model.__dirtyFields;
710
+
711
+ for (var column in dirtyFields) {
712
+ // Validate non-nullable constraints on updates
713
+ var fieldName = dirtyFields[column];
714
+ var entityDef = model.__entity[fieldName];
715
+ if(entityDef && entityDef.nullable === false && entityDef.primary !== true){
716
+ // Determine the value that will actually be persisted for this field
717
+ var persistedValue;
718
+ switch(entityDef.type){
719
+ case "integer":
720
+ persistedValue = model["_" + fieldName];
721
+ break;
722
+ case "belongsTo":
723
+ persistedValue = model["_" + fieldName] !== undefined ? model["_" + fieldName] : model[fieldName];
724
+ break;
725
+ default:
726
+ persistedValue = model[fieldName];
727
+ }
728
+ var isEmptyString = (typeof persistedValue === 'string') && (persistedValue.trim() === '');
729
+ if(persistedValue === undefined || persistedValue === null || isEmptyString){
730
+ throw `Entity ${model.__entity.__name} column ${fieldName} is a required Field`;
731
+ }
732
+ }
733
+
734
+ var type = model.__entity[dirtyFields[column]].type;
735
+
736
+ if(model.__entity[dirtyFields[column]].relationshipType === "belongsTo"){
737
+ type = "belongsTo";
738
+ }
739
+
740
+ // Build parameterized SET clause
741
+ switch(type){
742
+ case "belongsTo":
743
+ const foreignKey = model.__entity[dirtyFields[column]].foreignKey;
744
+ let fkValue = model[dirtyFields[column]];
745
+
746
+ // 🔥 Apply toDatabase transformer before validation
747
+ try {
748
+ fkValue = FieldTransformer.toDatabase(fkValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
749
+ } catch(transformError) {
750
+ throw new Error(`UPDATE failed: ${transformError.message}`);
751
+ }
752
+
753
+ try {
754
+ fkValue = $that._validateAndCoerceFieldType(fkValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
755
+ } catch(typeError) {
756
+ throw new Error(`UPDATE failed: ${typeError.message}`);
757
+ }
758
+ var fore = `_${dirtyFields[column]}`;
759
+ sqlParts.push(`[${foreignKey}] = ?`);
760
+ params.push(model[fore]);
761
+ break;
762
+ case "integer":
763
+ var intValue = model["_" + dirtyFields[column]];
764
+
765
+ // 🔥 Apply toDatabase transformer before validation
766
+ try {
767
+ intValue = FieldTransformer.toDatabase(intValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
768
+ } catch(transformError) {
769
+ throw new Error(`UPDATE failed: ${transformError.message}`);
770
+ }
771
+
772
+ try {
773
+ intValue = $that._validateAndCoerceFieldType(intValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
774
+ } catch(typeError) {
775
+ throw new Error(`UPDATE failed: ${typeError.message}`);
776
+ }
777
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
778
+ params.push(intValue);
779
+ break;
780
+ case "string":
781
+ var strValue = model[dirtyFields[column]];
782
+
783
+ // 🔥 Apply toDatabase transformer before validation
784
+ try {
785
+ strValue = FieldTransformer.toDatabase(strValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
786
+ } catch(transformError) {
787
+ throw new Error(`UPDATE failed: ${transformError.message}`);
788
+ }
789
+
790
+ try {
791
+ strValue = $that._validateAndCoerceFieldType(strValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
792
+ } catch(typeError) {
793
+ throw new Error(`UPDATE failed: ${typeError.message}`);
794
+ }
795
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
796
+ params.push(strValue);
797
+ break;
798
+ case "boolean":
799
+ var boolValue = model[dirtyFields[column]];
800
+
801
+ // 🔥 Apply toDatabase transformer before validation
802
+ try {
803
+ boolValue = FieldTransformer.toDatabase(boolValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
804
+ } catch(transformError) {
805
+ throw new Error(`UPDATE failed: ${transformError.message}`);
806
+ }
807
+
808
+ try {
809
+ boolValue = $that._validateAndCoerceFieldType(boolValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
810
+ } catch(typeError) {
811
+ throw new Error(`UPDATE failed: ${typeError.message}`);
812
+ }
813
+
814
+ // Convert to database-specific format (e.g., boolean → 1/0 for SQLite)
815
+ boolValue = $that._convertValueForDatabase(boolValue, model.__entity[dirtyFields[column]].type);
816
+
817
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
818
+ params.push(boolValue);
819
+ break;
820
+ case "time":
821
+ var timeValue = model[dirtyFields[column]];
822
+
823
+ // 🔥 Apply toDatabase transformer before validation
824
+ try {
825
+ timeValue = FieldTransformer.toDatabase(timeValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
826
+ } catch(transformError) {
827
+ throw new Error(`UPDATE failed: ${transformError.message}`);
828
+ }
829
+
830
+ try {
831
+ timeValue = $that._validateAndCoerceFieldType(timeValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
832
+ } catch(typeError) {
833
+ throw new Error(`UPDATE failed: ${typeError.message}`);
834
+ }
835
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
836
+ params.push(timeValue);
837
+ break;
838
+ case "hasMany":
839
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
840
+ params.push(model[dirtyFields[column]]);
841
+ break;
842
+ default:
843
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
844
+ params.push(model[dirtyFields[column]]);
845
+ }
846
+ }
847
+
848
+ if(sqlParts.length > 0){
849
+ return {
850
+ sql: sqlParts.join(', '),
851
+ params: params
852
+ };
853
+ }
854
+ else{
855
+ return -1;
856
+ }
857
+ }
858
+
859
+
655
860
  _buildDeleteObject(currentModel){
656
861
  var primaryKey = currentModel.__Key === undefined ? tools.getPrimaryKeyObject(currentModel.__entity) : currentModel.__Key;
657
862
  var value = currentModel.__value === undefined ? currentModel[primaryKey] : currentModel.__value;
@@ -713,7 +918,7 @@ class SQLLiteEngine {
713
918
  throw new Error(`Type mismatch for ${entityName}.${fieldName}: Expected string, got ${actualType} with value ${JSON.stringify(value)}`);
714
919
 
715
920
  case "boolean":
716
- // Coerce to boolean
921
+ // Coerce to boolean (then convert for database)
717
922
  if(actualType === 'boolean'){
718
923
  return value;
719
924
  }
@@ -751,6 +956,27 @@ class SQLLiteEngine {
751
956
  }
752
957
  }
753
958
 
959
+ /**
960
+ * Convert validated value to database-specific format
961
+ * Modern ORM pattern: transparent database-specific conversions
962
+ *
963
+ * @param {*} value - Already validated value
964
+ * @param {string} fieldType - Field type from entity definition
965
+ * @returns {*} Database-ready value
966
+ */
967
+ _convertValueForDatabase(value, fieldType){
968
+ if(value === undefined || value === null){
969
+ return value;
970
+ }
971
+
972
+ // SQLite boolean conversion: JavaScript boolean → INTEGER (1/0)
973
+ if(fieldType === 'boolean' && typeof value === 'boolean'){
974
+ return value ? 1 : 0;
975
+ }
976
+
977
+ return value;
978
+ }
979
+
754
980
 
755
981
  // return columns and value strings
756
982
  _buildSQLInsertObject(fields, modelEntity){
@@ -817,6 +1043,79 @@ class SQLLiteEngine {
817
1043
 
818
1044
  }
819
1045
 
1046
+ /**
1047
+ * NEW SECURE VERSION: Build SQL INSERT with parameterized queries
1048
+ * Returns {tableName, columns, placeholders, params}
1049
+ * This prevents SQL injection by separating SQL structure from values
1050
+ */
1051
+ _buildSQLInsertObjectParameterized(fields, modelEntity){
1052
+ var $that = this;
1053
+ var columnNames = [];
1054
+ var params = [];
1055
+
1056
+ for (var column in modelEntity) {
1057
+ // Skip internal properties
1058
+ if(column.indexOf("__") === -1 ){
1059
+ var fieldColumn = fields[column];
1060
+
1061
+ if((fieldColumn !== undefined && fieldColumn !== null ) && typeof(fieldColumn) !== "object"){
1062
+ // 🔥 Apply toDatabase transformer before validation
1063
+ try {
1064
+ fieldColumn = FieldTransformer.toDatabase(fieldColumn, modelEntity[column], modelEntity.__name, column);
1065
+ } catch(transformError) {
1066
+ throw new Error(`INSERT failed: ${transformError.message}`);
1067
+ }
1068
+
1069
+ // Validate and coerce field type before processing
1070
+ try {
1071
+ fieldColumn = $that._validateAndCoerceFieldType(fieldColumn, modelEntity[column], modelEntity.__name, column);
1072
+ } catch(typeError) {
1073
+ throw new Error(`INSERT failed: ${typeError.message}`);
1074
+ }
1075
+
1076
+ // Convert to database-specific format (e.g., boolean → 1/0 for SQLite)
1077
+ fieldColumn = $that._convertValueForDatabase(fieldColumn, modelEntity[column].type);
1078
+
1079
+ var relationship = modelEntity[column].relationshipType;
1080
+ var actualColumn = relationship === "belongsTo" ? modelEntity[column].foreignKey : column;
1081
+
1082
+ // Add column name and parameter
1083
+ columnNames.push(`[${actualColumn}]`);
1084
+ params.push(fieldColumn);
1085
+ }
1086
+ else{
1087
+ switch(modelEntity[column].type){
1088
+ case "belongsTo":
1089
+ var fieldObject = tools.findTrackedObject(fields.__context.__trackedEntities, column);
1090
+ if(Object.keys(fieldObject).length > 0){
1091
+ var primaryKey = tools.getPrimaryKeyObject(fieldObject.__entity);
1092
+ fieldColumn = fieldObject[primaryKey];
1093
+ var actualColumn = modelEntity[column].foreignKey;
1094
+ columnNames.push(`[${actualColumn}]`);
1095
+ params.push(fieldColumn);
1096
+ } else{
1097
+ console.log("Cannot find belongs to relationship")
1098
+ }
1099
+ break;
1100
+ }
1101
+ }
1102
+ }
1103
+ }
1104
+
1105
+ if(columnNames.length > 0){
1106
+ // Create placeholders: ?, ?, ?, ...
1107
+ var placeholders = params.map(() => '?').join(', ');
1108
+ return {
1109
+ tableName: modelEntity.__name,
1110
+ columns: columnNames.join(', '),
1111
+ placeholders: placeholders,
1112
+ params: params
1113
+ };
1114
+ } else {
1115
+ return -1;
1116
+ }
1117
+ }
1118
+
820
1119
  // will add double single quotes to allow string to be saved.
821
1120
  _santizeSingleQuotes(value, context){
822
1121
  if (typeof value === 'string' || value instanceof String){
@@ -854,11 +1153,23 @@ class SQLLiteEngine {
854
1153
  return this.db.exec(query);
855
1154
  }
856
1155
 
1156
+ _executeWithParams(query, params = []){
1157
+ console.log("SQL:", query);
1158
+ console.log("Params:", params);
1159
+ return this.db.prepare(query).run(...params);
1160
+ }
1161
+
857
1162
  _run(query){
858
1163
  console.log("SQL:", query);
859
1164
  return this.db.prepare(query).run();
860
1165
  }
861
1166
 
1167
+ _runWithParams(query, params = []){
1168
+ console.log("SQL:", query);
1169
+ console.log("Params:", params);
1170
+ return this.db.prepare(query).run(...params);
1171
+ }
1172
+
862
1173
  setDB(db, type){
863
1174
  this.db = db;
864
1175
  this.dbType = type; // this will let us know which type of sqlengine to use.