masterrecord 0.2.36 → 0.3.0

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 +19 -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 +309 -19
  13. package/context.js +47 -10
  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 +249 -17
  20. package/package.json +3 -3
  21. package/postgresEngine.js +434 -491
  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
@@ -0,0 +1,402 @@
1
+ // Version 1.0.0 - PostgreSQL migration query builder
2
+ class migrationPostgresQuery {
3
+
4
+ #tempTableName = "_temp_alter_column_update"
5
+
6
+ #getTableColumns(table){
7
+ var columnList = [];
8
+ for (var key in table) {
9
+ if(typeof table[key] === "object"){
10
+ var col = table[key];
11
+ // Skip relationship-only fields
12
+ if(col.type === 'hasOne' || col.type === 'hasMany' || col.type === 'hasManyThrough'){
13
+ continue;
14
+ }
15
+ // Map belongsTo to its foreignKey name
16
+ var name = (col.relationshipType === 'belongsTo' && col.foreignKey) ? col.foreignKey : col.name;
17
+ columnList.push(`"${name}"`);
18
+ }
19
+ }
20
+ return columnList.join(',');
21
+ }
22
+
23
+ #columnMapping(table){
24
+ /*
25
+ var mapping = {
26
+ "name": "id", // if this changes then call rename column
27
+ "type": "integer", // if this changes then call altercolumn
28
+ "primary": false, // is primary key
29
+ "nullable": false, // is nullable
30
+ "unique": true, // value has to be unique
31
+ "auto": true, // sets the value to SERIAL/BIGSERIAL
32
+ "cascadeOnDelete": true,
33
+ "lazyLoading": true,
34
+ "isNavigational": false
35
+ }
36
+ */
37
+
38
+ // PostgreSQL uses SERIAL for auto-increment, not separate AUTO_INCREMENT keyword
39
+ var auto = "";
40
+ var primaryKey = table.primary ? " PRIMARY KEY" : "";
41
+ var nullName = table.nullable ? "" : " NOT NULL";
42
+ var unique = table.unique ? " UNIQUE" : "";
43
+
44
+ // For PostgreSQL, if auto-increment primary key, use SERIAL or BIGSERIAL
45
+ var type;
46
+ if(table.auto && table.primary && (table.type === 'integer' || table.type === 'int')){
47
+ type = "SERIAL"; // Auto-incrementing integer
48
+ auto = "";
49
+ primaryKey = " PRIMARY KEY";
50
+ } else if(table.auto && table.primary && table.type === 'bigint'){
51
+ type = "BIGSERIAL";
52
+ auto = "";
53
+ primaryKey = " PRIMARY KEY";
54
+ } else {
55
+ type = this.typeManager(table.type);
56
+ }
57
+
58
+ var tableName = table.name;
59
+ if(table.relationshipType === 'belongsTo' && table.foreignKey){
60
+ tableName = table.foreignKey;
61
+ }
62
+
63
+ var defaultValue = "";
64
+ if(table.default !== undefined && table.default !== null){
65
+ let def = table.default;
66
+ if(table.type === 'boolean' || table.type === 'bool'){
67
+ // PostgreSQL uses TRUE/FALSE for booleans
68
+ def = (def === true || def === 'true') ? 'TRUE' : 'FALSE';
69
+ defaultValue = ` DEFAULT ${def}`;
70
+ }
71
+ else if(table.type === 'integer' || table.type === 'float' || table.type === 'decimal'){
72
+ defaultValue = ` DEFAULT ${def}`;
73
+ }
74
+ else{
75
+ const esc = String(def).replace(/'/g, "''");
76
+ defaultValue = ` DEFAULT '${esc}'`;
77
+ }
78
+ }
79
+
80
+ return `"${tableName}" ${type}${nullName}${defaultValue}${unique}${primaryKey}${auto}`;
81
+ }
82
+
83
+ typeManager(type){
84
+ switch(type) {
85
+ case "string":
86
+ return "VARCHAR(255)"
87
+ case "text":
88
+ return "TEXT"
89
+ case "float":
90
+ return "REAL" // PostgreSQL uses REAL for single-precision
91
+ case "decimal":
92
+ return "DECIMAL"
93
+ case "datetime":
94
+ return "TIMESTAMP"
95
+ case "timestamp":
96
+ return "TIMESTAMP"
97
+ case "date":
98
+ return "DATE"
99
+ case "time":
100
+ return "TIME"
101
+ case "boolean":
102
+ case "bool":
103
+ return "BOOLEAN" // PostgreSQL native boolean type
104
+ case "integer":
105
+ case "int":
106
+ return "INTEGER"
107
+ case "bigint":
108
+ return "BIGINT"
109
+ case "binary":
110
+ return "BYTEA" // PostgreSQL binary data type
111
+ case "blob":
112
+ return "BYTEA"
113
+ case "json":
114
+ return "JSON"
115
+ case "jsonb":
116
+ return "JSONB" // PostgreSQL binary JSON (more efficient)
117
+ case "uuid":
118
+ return "UUID" // PostgreSQL native UUID type
119
+ default:
120
+ return "TEXT"
121
+ }
122
+ }
123
+
124
+ // table is the altered field
125
+ alterColumn(table){
126
+ if(table){
127
+ // PostgreSQL uses different syntax for ALTER COLUMN
128
+ // ALTER TABLE table_name ALTER COLUMN column_name TYPE new_type;
129
+ const colName = table.table.name;
130
+ const tableName = table.tableName;
131
+ const type = this.typeManager(table.table.type);
132
+
133
+ // Build ALTER statements - PostgreSQL requires separate statements for different changes
134
+ let statements = [];
135
+
136
+ // Change type
137
+ statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" TYPE ${type}`);
138
+
139
+ // Change nullability
140
+ if(table.table.nullable === false){
141
+ statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" SET NOT NULL`);
142
+ } else if(table.table.nullable === true){
143
+ statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" DROP NOT NULL`);
144
+ }
145
+
146
+ // Change default
147
+ if(table.table.default !== undefined && table.table.default !== null){
148
+ let def = table.table.default;
149
+ if(table.table.type === 'boolean'){
150
+ def = (def === true || def === 'true') ? 'TRUE' : 'FALSE';
151
+ statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" SET DEFAULT ${def}`);
152
+ } else if(table.table.type === 'integer' || table.table.type === 'float'){
153
+ statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" SET DEFAULT ${def}`);
154
+ } else {
155
+ const esc = String(def).replace(/'/g, "''");
156
+ statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" SET DEFAULT '${esc}'`);
157
+ }
158
+ }
159
+
160
+ // Return array of statements or join them with semicolons
161
+ return statements.join('; ');
162
+ }
163
+ else{
164
+ console.log("table information is null");
165
+ return null;
166
+ }
167
+ }
168
+
169
+ addColum(table){
170
+ // PostgreSQL add column syntax
171
+ return `ALTER TABLE "${table.tableName}"
172
+ ADD COLUMN "${table.name}" ${table.realDataType}`;
173
+ }
174
+
175
+ dropColumn(table){
176
+ /*
177
+ PostgreSQL DROP COLUMN is more flexible than SQLite
178
+ Can drop columns with constraints if CASCADE is used
179
+ */
180
+ return `ALTER TABLE "${table.tableName}" DROP COLUMN "${table.name}"`;
181
+ }
182
+
183
+ insertInto(name, table){
184
+ return `INSERT INTO "${name}" (${this.#getTableColumns(table)})
185
+ SELECT ${this.#getTableColumns(table)} FROM "${this.#tempTableName}"`;
186
+ }
187
+
188
+ createTable(table){
189
+ var queryVar = "";
190
+
191
+ for (var key in table) {
192
+ if(typeof table[key] === "object"){
193
+ if(table[key].type !== "hasOne" && table[key].type !== "hasMany" && table[key].type !== "hasManyThrough"){
194
+ queryVar += `${this.#columnMapping(table[key])}, `;
195
+ }
196
+ }
197
+ }
198
+
199
+ var completeQuery = `CREATE TABLE IF NOT EXISTS "${table.__name}" (${queryVar.replace(/,\s*$/, "")});`;
200
+ return completeQuery;
201
+ }
202
+
203
+ dropTable(name){
204
+ return `DROP TABLE IF EXISTS "${name}"`;
205
+ }
206
+
207
+ renameTable(table){
208
+ return `ALTER TABLE "${table.tableName}" RENAME TO "${table.newName}"`;
209
+ }
210
+
211
+ renameColumn(table){
212
+ return `ALTER TABLE "${table.tableName}" RENAME COLUMN "${table.name}" TO "${table.newName}"`;
213
+ }
214
+
215
+ /**
216
+ * SEED DATA METHODS
217
+ * Support for inserting seed data during migrations
218
+ */
219
+
220
+ /**
221
+ * Insert seed data into a table
222
+ * @param {string} tableName - Name of the table
223
+ * @param {Object} data - Data object with column names as keys
224
+ * @returns {string} INSERT query
225
+ */
226
+ insertSeedData(tableName, data){
227
+ const columns = Object.keys(data).filter(k => !k.startsWith('__'));
228
+ const values = columns.map(col => {
229
+ const val = data[col];
230
+ if(val === null || val === undefined){
231
+ return 'NULL';
232
+ }
233
+ if(typeof val === 'boolean'){
234
+ return val ? 'TRUE' : 'FALSE';
235
+ }
236
+ if(typeof val === 'number'){
237
+ return val;
238
+ }
239
+ // Escape strings
240
+ const escaped = String(val).replace(/'/g, "''");
241
+ return `'${escaped}'`;
242
+ });
243
+
244
+ const columnList = columns.map(c => `"${c}"`).join(', ');
245
+ const valueList = values.join(', ');
246
+
247
+ return `INSERT INTO "${tableName}" (${columnList}) VALUES (${valueList})`;
248
+ }
249
+
250
+ /**
251
+ * Insert multiple seed records at once
252
+ * @param {string} tableName - Name of the table
253
+ * @param {Array} dataArray - Array of data objects
254
+ * @returns {string} Bulk INSERT query
255
+ */
256
+ bulkInsertSeedData(tableName, dataArray){
257
+ if(!dataArray || dataArray.length === 0){
258
+ return '';
259
+ }
260
+
261
+ const firstRow = dataArray[0];
262
+ const columns = Object.keys(firstRow).filter(k => !k.startsWith('__'));
263
+ const columnList = columns.map(c => `"${c}"`).join(', ');
264
+
265
+ const valueRows = dataArray.map(data => {
266
+ const values = columns.map(col => {
267
+ const val = data[col];
268
+ if(val === null || val === undefined){
269
+ return 'NULL';
270
+ }
271
+ if(typeof val === 'boolean'){
272
+ return val ? 'TRUE' : 'FALSE';
273
+ }
274
+ if(typeof val === 'number'){
275
+ return val;
276
+ }
277
+ const escaped = String(val).replace(/'/g, "''");
278
+ return `'${escaped}'`;
279
+ });
280
+ return `(${values.join(', ')})`;
281
+ });
282
+
283
+ return `INSERT INTO "${tableName}" (${columnList}) VALUES ${valueRows.join(', ')}`;
284
+ }
285
+
286
+ /**
287
+ * Update seed data (useful for down migrations)
288
+ * @param {string} tableName - Name of the table
289
+ * @param {Object} data - Data to update
290
+ * @param {Object} where - WHERE conditions
291
+ * @returns {string} UPDATE query
292
+ */
293
+ updateSeedData(tableName, data, where){
294
+ const setClause = Object.keys(data)
295
+ .filter(k => !k.startsWith('__'))
296
+ .map(col => {
297
+ const val = data[col];
298
+ if(val === null || val === undefined){
299
+ return `"${col}" = NULL`;
300
+ }
301
+ if(typeof val === 'boolean'){
302
+ return `"${col}" = ${val ? 'TRUE' : 'FALSE'}`;
303
+ }
304
+ if(typeof val === 'number'){
305
+ return `"${col}" = ${val}`;
306
+ }
307
+ const escaped = String(val).replace(/'/g, "''");
308
+ return `"${col}" = '${escaped}'`;
309
+ })
310
+ .join(', ');
311
+
312
+ const whereClause = Object.keys(where)
313
+ .map(col => {
314
+ const val = where[col];
315
+ if(val === null || val === undefined){
316
+ return `"${col}" IS NULL`;
317
+ }
318
+ if(typeof val === 'boolean'){
319
+ return `"${col}" = ${val ? 'TRUE' : 'FALSE'}`;
320
+ }
321
+ if(typeof val === 'number'){
322
+ return `"${col}" = ${val}`;
323
+ }
324
+ const escaped = String(val).replace(/'/g, "''");
325
+ return `"${col}" = '${escaped}'`;
326
+ })
327
+ .join(' AND ');
328
+
329
+ return `UPDATE "${tableName}" SET ${setClause} WHERE ${whereClause}`;
330
+ }
331
+
332
+ /**
333
+ * Delete seed data (useful for down migrations)
334
+ * @param {string} tableName - Name of the table
335
+ * @param {Object} where - WHERE conditions
336
+ * @returns {string} DELETE query
337
+ */
338
+ deleteSeedData(tableName, where){
339
+ const whereClause = Object.keys(where)
340
+ .map(col => {
341
+ const val = where[col];
342
+ if(val === null || val === undefined){
343
+ return `"${col}" IS NULL`;
344
+ }
345
+ if(typeof val === 'boolean'){
346
+ return `"${col}" = ${val ? 'TRUE' : 'FALSE'}`;
347
+ }
348
+ if(typeof val === 'number'){
349
+ return `"${col}" = ${val}`;
350
+ }
351
+ const escaped = String(val).replace(/'/g, "''");
352
+ return `"${col}" = '${escaped}'`;
353
+ })
354
+ .join(' AND ');
355
+
356
+ return `DELETE FROM "${tableName}" WHERE ${whereClause}`;
357
+ }
358
+ }
359
+
360
+ module.exports = migrationPostgresQuery;
361
+
362
+ /**
363
+ * PostgreSQL Data Types Reference:
364
+ *
365
+ * NATIVE_DATABASE_TYPES = {
366
+ * primary_key: "SERIAL PRIMARY KEY" or "BIGSERIAL PRIMARY KEY",
367
+ * string: VARCHAR(255),
368
+ * text: TEXT,
369
+ * integer: INTEGER,
370
+ * bigint: BIGINT,
371
+ * float: REAL,
372
+ * decimal: DECIMAL,
373
+ * datetime: TIMESTAMP,
374
+ * timestamp: TIMESTAMP,
375
+ * timestamptz: TIMESTAMPTZ,
376
+ * time: TIME,
377
+ * date: DATE,
378
+ * binary: BYTEA,
379
+ * boolean: BOOLEAN,
380
+ * json: JSON,
381
+ * jsonb: JSONB (recommended over JSON for performance),
382
+ * uuid: UUID,
383
+ * xml: XML
384
+ * }
385
+ *
386
+ * Key PostgreSQL Differences:
387
+ * 1. AUTO_INCREMENT → SERIAL or BIGSERIAL
388
+ * 2. Backticks (`) → Double quotes (") for identifiers
389
+ * 3. TINYINT → BOOLEAN (true/false instead of 0/1)
390
+ * 4. BLOB → BYTEA
391
+ * 5. Multiple ALTER COLUMN statements (can't combine TYPE and NOT NULL)
392
+ * 6. Native JSON and JSONB support
393
+ * 7. Native UUID support
394
+ * 8. IF EXISTS supported in DROP TABLE
395
+ * 9. More flexible DROP COLUMN (supports CASCADE)
396
+ *
397
+ * Seed Data Methods:
398
+ * - insertSeedData(tableName, data): Insert single record
399
+ * - bulkInsertSeedData(tableName, dataArray): Insert multiple records
400
+ * - updateSeedData(tableName, data, where): Update existing records
401
+ * - deleteSeedData(tableName, where): Delete records
402
+ */
@@ -180,7 +180,151 @@ class migrationSQLiteQuery {
180
180
  return `ALTER TABLE ${table.tableName} RENAME COLUMN ${table.name} TO ${table.newName}`
181
181
  }
182
182
 
183
-
183
+ /**
184
+ * SEED DATA METHODS
185
+ * Support for inserting seed data during migrations
186
+ */
187
+
188
+ /**
189
+ * Insert seed data into a table
190
+ * @param {string} tableName - Name of the table
191
+ * @param {Object} data - Data object with column names as keys
192
+ * @returns {string} INSERT query
193
+ */
194
+ insertSeedData(tableName, data){
195
+ const columns = Object.keys(data).filter(k => !k.startsWith('__'));
196
+ const values = columns.map(col => {
197
+ const val = data[col];
198
+ if(val === null || val === undefined){
199
+ return 'NULL';
200
+ }
201
+ if(typeof val === 'boolean'){
202
+ return val ? '1' : '0'; // SQLite INTEGER for boolean
203
+ }
204
+ if(typeof val === 'number'){
205
+ return val;
206
+ }
207
+ // Escape strings
208
+ const escaped = String(val).replace(/'/g, "''");
209
+ return `'${escaped}'`;
210
+ });
211
+
212
+ const columnList = columns.join(', ');
213
+ const valueList = values.join(', ');
214
+
215
+ return `INSERT INTO ${tableName} (${columnList}) VALUES (${valueList})`;
216
+ }
217
+
218
+ /**
219
+ * Insert multiple seed records at once
220
+ * @param {string} tableName - Name of the table
221
+ * @param {Array} dataArray - Array of data objects
222
+ * @returns {string} Bulk INSERT query
223
+ */
224
+ bulkInsertSeedData(tableName, dataArray){
225
+ if(!dataArray || dataArray.length === 0){
226
+ return '';
227
+ }
228
+
229
+ const firstRow = dataArray[0];
230
+ const columns = Object.keys(firstRow).filter(k => !k.startsWith('__'));
231
+ const columnList = columns.join(', ');
232
+
233
+ const valueRows = dataArray.map(data => {
234
+ const values = columns.map(col => {
235
+ const val = data[col];
236
+ if(val === null || val === undefined){
237
+ return 'NULL';
238
+ }
239
+ if(typeof val === 'boolean'){
240
+ return val ? '1' : '0';
241
+ }
242
+ if(typeof val === 'number'){
243
+ return val;
244
+ }
245
+ const escaped = String(val).replace(/'/g, "''");
246
+ return `'${escaped}'`;
247
+ });
248
+ return `(${values.join(', ')})`;
249
+ });
250
+
251
+ return `INSERT INTO ${tableName} (${columnList}) VALUES ${valueRows.join(', ')}`;
252
+ }
253
+
254
+ /**
255
+ * Update seed data (useful for down migrations)
256
+ * @param {string} tableName - Name of the table
257
+ * @param {Object} data - Data to update
258
+ * @param {Object} where - WHERE conditions
259
+ * @returns {string} UPDATE query
260
+ */
261
+ updateSeedData(tableName, data, where){
262
+ const setClause = Object.keys(data)
263
+ .filter(k => !k.startsWith('__'))
264
+ .map(col => {
265
+ const val = data[col];
266
+ if(val === null || val === undefined){
267
+ return `${col} = NULL`;
268
+ }
269
+ if(typeof val === 'boolean'){
270
+ return `${col} = ${val ? '1' : '0'}`;
271
+ }
272
+ if(typeof val === 'number'){
273
+ return `${col} = ${val}`;
274
+ }
275
+ const escaped = String(val).replace(/'/g, "''");
276
+ return `${col} = '${escaped}'`;
277
+ })
278
+ .join(', ');
279
+
280
+ const whereClause = Object.keys(where)
281
+ .map(col => {
282
+ const val = where[col];
283
+ if(val === null || val === undefined){
284
+ return `${col} IS NULL`;
285
+ }
286
+ if(typeof val === 'boolean'){
287
+ return `${col} = ${val ? '1' : '0'}`;
288
+ }
289
+ if(typeof val === 'number'){
290
+ return `${col} = ${val}`;
291
+ }
292
+ const escaped = String(val).replace(/'/g, "''");
293
+ return `${col} = '${escaped}'`;
294
+ })
295
+ .join(' AND ');
296
+
297
+ return `UPDATE ${tableName} SET ${setClause} WHERE ${whereClause}`;
298
+ }
299
+
300
+ /**
301
+ * Delete seed data (useful for down migrations)
302
+ * @param {string} tableName - Name of the table
303
+ * @param {Object} where - WHERE conditions
304
+ * @returns {string} DELETE query
305
+ */
306
+ deleteSeedData(tableName, where){
307
+ const whereClause = Object.keys(where)
308
+ .map(col => {
309
+ const val = where[col];
310
+ if(val === null || val === undefined){
311
+ return `${col} IS NULL`;
312
+ }
313
+ if(typeof val === 'boolean'){
314
+ return `${col} = ${val ? '1' : '0'}`;
315
+ }
316
+ if(typeof val === 'number'){
317
+ return `${col} = ${val}`;
318
+ }
319
+ const escaped = String(val).replace(/'/g, "''");
320
+ return `${col} = '${escaped}'`;
321
+ })
322
+ .join(' AND ');
323
+
324
+ return `DELETE FROM ${tableName} WHERE ${whereClause}`;
325
+ }
326
+
327
+
184
328
  }
185
329
 
186
330