masquerade-orm 0.1.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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/bin/universalTsInit.js +59 -0
  4. package/docs/deletion.md +186 -0
  5. package/docs/find.md +265 -0
  6. package/docs/getting-started-javascript.md +112 -0
  7. package/docs/getting-started-typescript.md +159 -0
  8. package/docs/in-depth-class-definitions.md +193 -0
  9. package/docs/jsdoc-ux-tips.md +17 -0
  10. package/docs/managing-the-database.md +37 -0
  11. package/docs/saving-to-database.md +37 -0
  12. package/index.d.ts +7 -0
  13. package/index.js +11 -0
  14. package/jsconfig.json +29 -0
  15. package/package.json +40 -0
  16. package/src/ORM/DbManager.js +76 -0
  17. package/src/ORM/ORM.js +181 -0
  18. package/src/ORM/bootOrm.js +929 -0
  19. package/src/changeLogger/changeLogger.js +55 -0
  20. package/src/changeLogger/save.js +237 -0
  21. package/src/changeLogger/sqlClients/postgres.js +97 -0
  22. package/src/changeLogger/sqlClients/sqlite.js +120 -0
  23. package/src/entity/delete/delete.js +20 -0
  24. package/src/entity/delete/getDependents.js +46 -0
  25. package/src/entity/entity.d.ts +46 -0
  26. package/src/entity/entity.js +228 -0
  27. package/src/entity/find/find.js +157 -0
  28. package/src/entity/find/joins.js +52 -0
  29. package/src/entity/find/queryBuilder.js +95 -0
  30. package/src/entity/find/relations.js +23 -0
  31. package/src/entity/find/scopeProxies.js +60 -0
  32. package/src/entity/find/sqlClients/postgresFuncs.js +171 -0
  33. package/src/entity/find/sqlClients/sqliteFuncs.js +211 -0
  34. package/src/entity/find/where/relationalWhere.js +142 -0
  35. package/src/entity/find/where/where.js +244 -0
  36. package/src/entity/find/where/whereArgsFunctions.js +49 -0
  37. package/src/misc/classes.js +63 -0
  38. package/src/misc/constants.js +42 -0
  39. package/src/misc/miscFunctions.js +167 -0
  40. package/src/misc/ormStore.js +18 -0
  41. package/src/misc/types.d.ts +560 -0
  42. package/src/proxies/instanceProxy.js +544 -0
  43. package/src/proxies/nonRelationalArrayProxy.js +76 -0
  44. package/src/proxies/objectProxy.js +50 -0
  45. package/src/proxies/relationalArrayProxy.js +130 -0
  46. package/src/webpack/masquerade-loader.js +14 -0
  47. package/src/webpack/plugin.js +43 -0
  48. package/src/webpack/store.js +2 -0
  49. package/src/webpack/webpack.config.js +41 -0
  50. package/testing/dev-doc.md +7 -0
  51. package/testing/generationFuncs.js +21 -0
  52. package/testing/miscFunctions.js +97 -0
  53. package/testing/postgres.test.js +254 -0
  54. package/testing/sqlite.test.js +257 -0
  55. package/testing/testing-classes.js +102 -0
@@ -0,0 +1,929 @@
1
+
2
+ import { createSourceFile, SyntaxKind, ScriptKind, ScriptTarget } from "typescript"
3
+ import { js2SqlTyping, nonSnake2Snake, snake2Pascal, array2String, coloredBackgroundConsoleLog } from "../misc/miscFunctions.js"
4
+ import { dependenciesSymb, floatColumnTypes, referencesSymb } from "../misc/constants.js"
5
+ import { uuidv7 } from "uuidv7"
6
+ import { DbManagerStore } from "./DbManager.js"
7
+ import { OrmStore } from "../misc/ormStore.js"
8
+ /**@typedef {import('../misc/types.js').TABLE} TABLE */
9
+ /**@typedef {import('../misc/types.js').DbPrimaryKey} DbPrimaryKey */
10
+
11
+
12
+ export async function compareAgainstDb(tablesDict) {
13
+ const { dbConnection, sqlClient } = OrmStore.store
14
+ let queryFunc
15
+ let strFunc
16
+ let dbTableNames
17
+ if (sqlClient === `postgresql`) {
18
+ queryFunc = dbConnection.query.bind(dbConnection)
19
+ strFunc = (tableName) => `SELECT column_name
20
+ FROM information_schema.columns
21
+ WHERE table_schema = 'public'
22
+ AND table_name = '${tableName}';`
23
+
24
+ dbTableNames = (await queryFunc(`SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'public' ORDER BY tablename;`))
25
+ .rows.map(rowObj => rowObj.tablename)
26
+ }
27
+ else {
28
+ queryFunc = (sql) => {
29
+ const stmt = dbConnection.prepare(sql)
30
+ const rows = stmt.all()
31
+ return rows.map(r => r.name)
32
+ }
33
+ strFunc = (tableName) => `SELECT name FROM pragma_table_info('${tableName}');`
34
+ dbTableNames = dbConnection.prepare(`PRAGMA table_list;`).all()
35
+ .filter(tableObj => tableObj.schema === `main` && tableObj.name !== `sqlite_schema`)
36
+ .map(tableObj => tableObj.name)
37
+ }
38
+
39
+ for (const [tableName, tableObj] of Object.entries(tablesDict)) {
40
+ const tableColumns = sqlClient === `postgresql`
41
+ ? (await queryFunc(strFunc(tableName))).rows.map(rowObj => snake2Pascal(rowObj.column_name, true))
42
+ : await queryFunc(strFunc(tableName)).map(tableName => snake2Pascal(tableName, true))
43
+
44
+ const index = dbTableNames.findIndex(dbTableName => dbTableName === tableName)
45
+ if (index === -1) {
46
+ tableObj.alreadyExists = false
47
+ continue
48
+ }
49
+ else {
50
+ tableObj.alreadyExists = true
51
+ dbTableNames.splice(index, 1)
52
+ }
53
+
54
+ const classColumns = Object.keys(tableObj.columns)
55
+ const unusedColumns = tableColumns.filter(columnName => !classColumns.includes(columnName))
56
+ const newColumns = classColumns.filter(columnName => !tableColumns.includes(columnName))
57
+
58
+ if (newColumns.length) { // newColumns includes not only new columns, but also properties corresponding to junction tables, that may or may not exist on db.
59
+ for (const column of newColumns) {
60
+ const columnObj = tableObj.columns[column]
61
+ if (columnObj.relational) {
62
+ const junctionTableName = `${tableName}___${nonSnake2Snake(columnObj.name)}_jt`
63
+ const index = dbTableNames.findIndex(dbTableName => dbTableName === junctionTableName)
64
+ if (index !== -1) {
65
+ dbTableNames.splice(index, 1)
66
+ delete tableObj.columns[column]
67
+ continue
68
+ }
69
+ }
70
+ const newColumnsDict = tableObj.newColumns ??= {}
71
+ newColumnsDict[column] = columnObj
72
+ delete tableObj.columns[column]
73
+ }
74
+ }
75
+
76
+ if (unusedColumns.length) {
77
+ const className = snake2Pascal(tableName, true)
78
+ const dropColumnsDict = DbManagerStore.dropColumnsDict
79
+ dropColumnsDict[className] = unusedColumns
80
+ const loggedArr = array2String(unusedColumns.map(columnName => nonSnake2Snake(columnName)))
81
+ coloredBackgroundConsoleLog(`Warning: Unused columns found in table '${tableName}' (${className}): ${loggedArr}. Consider removing them manually or using DbManager\`s 'dropUnusedColumns' method.\n`, "warning")
82
+ }
83
+ }
84
+ const unusedJunctions = dbTableNames.filter(tableName => tableName.includes(`___`) && tableName.endsWith(`_jt`))
85
+ const unusedTables = dbTableNames.filter(tableName => !unusedJunctions.includes(tableName))
86
+ if (unusedTables.length) {
87
+ coloredBackgroundConsoleLog(`Warning: The following entity tables are unused: ${array2String(unusedTables)}. Consider removing them manually or using DbManager\`s 'dropUnusedTables' method.\n`, "warning")
88
+ DbManagerStore.deleteTables = unusedTables
89
+ }
90
+ if (unusedJunctions.length) {
91
+ coloredBackgroundConsoleLog(`Warning: The following junction tables are unused: ${array2String(unusedJunctions)}. Consider removing them manually or using DbManager\`s 'dropUnusedJunctions' method.\n`, "warning")
92
+ DbManagerStore.deleteJunctions = unusedJunctions
93
+ }
94
+ }
95
+
96
+
97
+ export async function getInitIdValues(tablesDict) {
98
+ const ormStore = OrmStore.store
99
+ const idLogger = ormStore.idLogger = {}
100
+ const { dbConnection, sqlClient } = ormStore
101
+ let queryFunc
102
+ if (sqlClient === `postgresql`) queryFunc = dbConnection.query.bind(dbConnection)
103
+ else queryFunc = (query) => dbConnection.prepare(query).all()
104
+
105
+ const queryFuncWithTryCatch = async (query) => {
106
+ try {
107
+ return await queryFunc(query)
108
+ }
109
+ catch (e) { }
110
+ }
111
+
112
+ for (const [tableName, tableObj] of Object.entries(tablesDict)) {
113
+ const idType = tableObj.columns.id.type
114
+ if (idType === `string`) {
115
+ idLogger[snake2Pascal(tableName)] = uuidv7
116
+ continue
117
+ }
118
+
119
+ const queryStr = `SELECT id FROM ${tableName} ORDER BY id DESC LIMIT 1;`
120
+ const res = await queryFuncWithTryCatch(queryStr)
121
+ if (!res) {
122
+ const idVal = idType === `number` ? 0 : 0n
123
+ idLogger[snake2Pascal(tableName)] = idVal
124
+ continue
125
+ }
126
+ let id = sqlClient === `postgresql` ? res.rows[0]?.id : res[0]?.id
127
+ if (id) {
128
+ id = idType === `number` ? parseInt(id, 10) : BigInt(id)
129
+ idLogger[snake2Pascal(tableName)] = id
130
+ }
131
+ else {
132
+ const idVal = idType === `number` ? 0 : 0n
133
+ idLogger[snake2Pascal(tableName)] = idVal
134
+ }
135
+ }
136
+ }
137
+
138
+ export function nodeArr2ClassDict(nodeArr) {
139
+ const classObjArr = filterNodesAndConvert2ClassObjects(nodeArr)
140
+ let classArr = []
141
+ const entityArr = classObjArr.map(classObj => snake2Pascal(classObj.name))
142
+ for (const classObj of classObjArr) {
143
+ fillClassObjColumns(classObj, entityArr)
144
+ delete classObj.node
145
+ classArr.push(classObj)
146
+ }
147
+ // classArr.push(returnEntityClassObj())
148
+ classArr = classArr.map(classObj => [classObj.name, classObj])
149
+ const classDict = Object.fromEntries(classArr)
150
+ return classDict
151
+ }
152
+
153
+ export function returnEntityClassObj() {
154
+ let { idTypeDefault } = OrmStore.store
155
+ if (idTypeDefault === "UUID") idTypeDefault = "string"
156
+ else if (idTypeDefault === "INT") idTypeDefault = "number"
157
+ else if (idTypeDefault === "BIGINT") idTypeDefault = "bigint"
158
+ else throw new Error(`\n'${idTypeDefault}' is not a valid primary type.`)
159
+
160
+ const idColumn = { name: "id", type: idTypeDefault }
161
+ const updatedAtColumn = { name: "updatedAt", type: "Date" }
162
+ return { name: "entity", abstract: true, columns: [idColumn, updatedAtColumn] }
163
+ }
164
+
165
+ export function filterNodesAndConvert2ClassObjects(nodeArr) {
166
+ const classObjArr = []
167
+ let currentUnfilteredNodes = nodeArr
168
+ let newUnfilteredNodes = []
169
+ let currentValidParents = ['entity']
170
+ let newValidParents = []
171
+ let iteration = 0
172
+ while (currentValidParents.length) {
173
+ for (const node of currentUnfilteredNodes) {
174
+ const parent = nonSnake2Snake(node.heritageClauses[0].types[0].expression.escapedText)
175
+ if (currentValidParents.includes(parent) || newValidParents.includes(parent)) {
176
+ const name = nonSnake2Snake(node.name.escapedText)
177
+ newValidParents.push(name)
178
+ const classObj = { name, parent, node, columns: [], abstract: false }
179
+ if (node.modifiers) {
180
+ for (const modifier of node.modifiers)
181
+ if (modifier.kind == SyntaxKind.AbstractKeyword) classObj.abstract = true
182
+ }
183
+ classObjArr.push(classObj)
184
+ }
185
+ else newUnfilteredNodes.push(node)
186
+ }
187
+ currentValidParents = newValidParents
188
+ newValidParents = []
189
+ currentUnfilteredNodes = newUnfilteredNodes
190
+ newUnfilteredNodes = []
191
+ iteration++
192
+ }
193
+ return classObjArr
194
+ }
195
+
196
+ export function handleSpecialClassSettingsObj(nodeInitializer) {
197
+ const specialSettingsObj = {}
198
+ const keysNodes = nodeInitializer.properties ?? nodeInitializer.elements
199
+ for (const keyNode of keysNodes) {
200
+ const key = keyNode.name.escapedText
201
+ const value = keyNode.initializer.text ?? handleSpecialClassSettingsObj(keyNode.initializer)
202
+ specialSettingsObj[key] = value
203
+ }
204
+ return specialSettingsObj
205
+ }
206
+
207
+ export function parseTypeObjContext(/**@type {object | string}*/ typeObjOrString, columnObj, entityNamesArr, nonRelationalTypesArr, className, /**@type {string | undefined}*/ tagName, /**@type {string[] | undefined}*/ typeScriptInvalidTypeArr = undefined) {
208
+ /**@type {string}*/ let typeName = typeof typeObjOrString === `string` ? typeObjOrString.trim() : typeObjOrString.getText().trim()
209
+ if (typeName.endsWith(`[]`)) {
210
+ typeName = typeName.slice(0, -2)
211
+ columnObj.isArray = true
212
+
213
+ if (typeName.startsWith(`(`) && typeName.endsWith(`)`)) {
214
+ typeName = typeName.slice(1, -1)
215
+ const separatedTypes = typeName.split(`|`)
216
+ for (let type of separatedTypes) {
217
+ type = type.trim()
218
+ if (type === `undefined`) continue
219
+ else mapType2ValidMainType(type, columnObj, className, nonRelationalTypesArr, entityNamesArr, tagName, typeScriptInvalidTypeArr)
220
+ }
221
+ }
222
+ else mapType2ValidMainType(typeName, columnObj, className, nonRelationalTypesArr, entityNamesArr, tagName, typeScriptInvalidTypeArr)
223
+ }
224
+ else if (typeName === `undefined`) columnObj.nullable = true
225
+ else if (typeName === `Unique`) columnObj.unique = true
226
+ else mapType2ValidMainType(typeName, columnObj, className, nonRelationalTypesArr, entityNamesArr, tagName, typeScriptInvalidTypeArr)
227
+ }
228
+
229
+ export function mapType2ValidMainType(typeName, columnObj, className, nonRelationalTypesArr, entityNamesArr, tagName, typeScriptInvalidTypeArr) {
230
+ if (nonRelationalTypesArr.includes(typeName)) assignColumnType(typeName, columnObj, className)
231
+ else if (entityNamesArr.includes(typeName)) {
232
+ columnObj.relational = true
233
+ assignColumnType(typeName, columnObj, className)
234
+ }
235
+ else {
236
+ if (tagName === `satisfies`) assignColumnType(`object`, columnObj, className)
237
+ else {
238
+ if (typeScriptInvalidTypeArr) {
239
+ if (typeName.includes('object')) assignColumnType('object', columnObj, className)
240
+ typeScriptInvalidTypeArr.push(typeName)
241
+ }
242
+ else {
243
+ console.error(`\nInvalid typing error on property '${columnObj.name}' of class ${snake2Pascal(className)} - ${typeName} is not a valid main type.\n`
244
+ + `Valid main types are ${array2String([...nonRelationalTypesArr, ...entityNamesArr])}.`)
245
+ process.exit(1)
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ export function assignColumnType(type, columnObj, className) {
252
+ if (columnObj.type) {
253
+ console.error(`\nInvalid typing error on property '${columnObj.name}' of class ${snake2Pascal(className)} - cannot have two main types of ${type} and ${columnObj.type}.`)
254
+ process.exit(1)
255
+ }
256
+ columnObj.type = type
257
+ }
258
+
259
+ export function fillClassObjColumns(classObj, entityNamesArr) {
260
+ const classNodesArr = classObj.node.members.length ? classObj.node.members : []
261
+ const nonRelationalTypesArr = [`number`, `integer`, `string`, `boolean`, `object`, `Date`, `bigint`]
262
+
263
+ for (const node of classNodesArr) {
264
+ if (node.kind == SyntaxKind.Constructor && node.jsDoc) {
265
+ node.jsDoc[0].tags[0].tagName.escapedText === "abstract" && (classObj.abstract = true)
266
+ }
267
+
268
+ if (node.kind == SyntaxKind.PropertyDeclaration) {
269
+ let propertyName
270
+ /**@type {any}*/ let columnObj
271
+ let isStatic = false
272
+
273
+ if (node.modifiers) {
274
+ for (const modifier of node.modifiers) {
275
+ if (modifier.kind === SyntaxKind.StaticKeyword) isStatic = true
276
+ }
277
+ }
278
+
279
+ if ((node.name.expression && node.name.expression.escapedText === "ormClassSettings_") || (node.name.escapedText === "ormClassSettings_")) {
280
+ propertyName = "ormClassSettings_"
281
+
282
+ if (!isStatic) {
283
+ console.error(`\nProperty '${propertyName}' of class ${snake2Pascal(classObj.name)} needs to be a static property.`)
284
+ process.exit(1)
285
+ }
286
+ columnObj = {}
287
+ const value = node.initializer ?? {}
288
+ //TODO special class settings
289
+ if (value.kind == SyntaxKind.ObjectLiteralExpression) columnObj = handleSpecialClassSettingsObj(value)
290
+ classObj.specialSettings = columnObj
291
+ }
292
+ else {
293
+ if (isStatic) continue
294
+
295
+ const typeScriptInvalidTypesArr = []
296
+ propertyName = node.name.escapedText
297
+ columnObj = { name: propertyName }
298
+
299
+ if (node.jsDoc) {
300
+ const tagObj = node.jsDoc[0].tags[0]
301
+ const tagName = tagObj.tagName ? tagObj.tagName.escapedText : undefined
302
+ const typeObj = tagObj.typeExpression.type
303
+ if (typeObj.types) {
304
+ if (typeObj.types.length > 3) {
305
+ console.error(`\nInvalid typing error on property '${propertyName}' of class ${snake2Pascal(classObj.name)} - a property cannot have more than three types (one main type + undefined + Unique).`)
306
+ process.exit(1)
307
+ }
308
+ const typesObj = typeObj.types
309
+ for (const typeObj of typesObj) parseTypeObjContext(typeObj, columnObj, entityNamesArr, nonRelationalTypesArr, classObj.name, tagName)
310
+ }
311
+ else parseTypeObjContext(typeObj, columnObj, entityNamesArr, nonRelationalTypesArr, classObj.name, tagName)
312
+ }
313
+ else if (node.type) {
314
+ const typeObj = node.type
315
+ if (typeObj.types) {
316
+ if (typeObj.types.length > 3) {
317
+ console.error(`\nInvalid typing error on property '${propertyName}' of class ${snake2Pascal(classObj.name)} - a property cannot have more than three types (main type + undefined + Unique).`)
318
+ process.exit(1)
319
+ }
320
+ const typesObj = typeObj.types
321
+ for (const typeObj of typesObj) parseTypeObjContext(typeObj, columnObj, entityNamesArr, nonRelationalTypesArr, classObj.name, undefined, typeScriptInvalidTypesArr)
322
+ }
323
+ else parseTypeObjContext(typeObj, columnObj, entityNamesArr, nonRelationalTypesArr, classObj.name, undefined, typeScriptInvalidTypesArr)
324
+ }
325
+ else if (node.initializer.kind === SyntaxKind.SatisfiesExpression) {
326
+ const typeObj = node.initializer
327
+ const satisfiesText = typeObj.getText()
328
+ const typeText = satisfiesText.split(`satisfies`)[1]
329
+ parseTypeObjContext(typeText, columnObj, entityNamesArr, nonRelationalTypesArr, classObj.name, `satisfies`, typeScriptInvalidTypesArr)
330
+ }
331
+ else {
332
+ console.error(`\nInvalid typing error on property '${propertyName}' of class ${snake2Pascal(classObj.name)} - property has no typing.`)
333
+ process.exit(1)
334
+ }
335
+
336
+ if (node.questionToken) columnObj.nullable = true
337
+
338
+ if (!columnObj.type) noMainTypeOnColumnObjErr(propertyName, classObj, typeScriptInvalidTypesArr, [...nonRelationalTypesArr, ...entityNamesArr])
339
+ else classObj.columns.push(columnObj)
340
+ }
341
+ }
342
+ }
343
+ }
344
+
345
+ export function noMainTypeOnColumnObjErr(propertyName, classObj, typeScriptInvalidTypesArr, validMainTypesArr) {
346
+ console.error(`\nInvalid typing error on property '${propertyName}' of class ${snake2Pascal(classObj.name)} - property has no main type.`)
347
+ if (typeScriptInvalidTypesArr.length) console.error(`\nThe types ${array2String(typeScriptInvalidTypesArr)} may need to be paired with a main type from the following ${array2String(validMainTypesArr)} .`)
348
+ process.exit(1)
349
+ }
350
+
351
+ export function entities2NodeArr(/**@type {{ [key: string]: function }}*/ entityObject) {
352
+ if (!Object.keys(entityObject).length) return {}
353
+ const nodeArr = []
354
+ const nodes = Object.values(entityObject).map(entity => createSourceFile('', entity.toString(), ScriptTarget.Latest, true, ScriptKind.TSX).statements[0])
355
+ for (const node of nodes) {
356
+ //@ts-ignore
357
+ if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && node.heritageClauses)
358
+ nodeArr.push(node)
359
+ }
360
+ return nodeArr
361
+ }
362
+
363
+ export function addChildrenToClasses(classDict) {
364
+ const childrenArrDict = {}
365
+ for (const classObj of Object.values(classDict)) {
366
+ if (classObj.parent) {
367
+ childrenArrDict[classObj.parent] ??= []
368
+ childrenArrDict[classObj.parent].push(classObj.name)
369
+ }
370
+ }
371
+ for (const [className, childrenArr] of Object.entries(childrenArrDict))
372
+ classDict[className].children = childrenArr
373
+ }
374
+
375
+ export function recursiveBranch(classObj, branchArr, classDict, previousClassObj = undefined, branchLength = 0) {
376
+ // takes leaves and turns them into branches
377
+ // previousClassObj is actually the original leaf
378
+ branchLength++
379
+ if (previousClassObj) {
380
+ /**@type {any}*/ let lastParent = previousClassObj
381
+ for (let i = 0; i < branchLength - 1; i++) lastParent = lastParent.parent
382
+
383
+ if (lastParent.parent) {
384
+ const newParentObj = classDict[lastParent.parent]
385
+ lastParent.parent = newParentObj
386
+ recursiveBranch(newParentObj, branchArr, classDict, previousClassObj, branchLength)
387
+ }
388
+ else branchArr.push(previousClassObj)
389
+ }
390
+ else {
391
+ if (classObj.parent) {
392
+ const parent = classDict[classObj.parent]
393
+ classObj.parent = parent
394
+ recursiveBranch(parent, branchArr, classDict, classObj, branchLength)
395
+ }
396
+ else branchArr.push(classObj)
397
+ }
398
+ }
399
+
400
+ export function createBranches(classDict) {
401
+ const branches = []
402
+
403
+ for (const classObj of Object.values(classDict)) {
404
+ if (!classObj.children) {
405
+ if (classObj.abstract) throw new Error(`\nInvalid class declaration - '${snake2Pascal(classObj.name)}' cannot be abstract since abstract classes must have children.`)
406
+ recursiveBranch(classObj, branches, classDict)
407
+ }
408
+ }
409
+ return branches
410
+ }
411
+
412
+ export function inheritColumns(inheritingClass) {
413
+ let iterativeClassObj = inheritingClass
414
+ let parent
415
+
416
+ while (iterativeClassObj.parent) {
417
+ parent = iterativeClassObj.parent
418
+ if (!parent.abstract) {
419
+ const { columns } = returnEntityClassObj()
420
+ const [id, updatedAt] = columns
421
+ inheritingClass.columns.unshift(id)
422
+ return parent
423
+ }
424
+
425
+ inheritingClass.columns.push(...parent.columns)
426
+ if (iterativeClassObj.specialSettings && iterativeClassObj.specialSettings.idType) {
427
+ const index = inheritingClass.columns.find(columnObj => columnObj.name === 'id')
428
+ inheritingClass.columns[index] = idType2IdColumnObj(iterativeClassObj.specialSettings.idType)
429
+ }
430
+ iterativeClassObj = parent
431
+ }
432
+ }
433
+
434
+ export function linkClassMap(classMapsObj, dependenciesObj, referencesObj) {
435
+
436
+ for (const classMap of Object.values(classMapsObj)) {
437
+ const className = classMap.className
438
+
439
+ if (classMap.parent) {
440
+ const parentName = classMap.parent
441
+ if (parentName === `Entity`) delete classMap.parent
442
+ else classMap.parent = classMapsObj[parentName]
443
+ }
444
+ if (dependenciesObj[className]) classMap[dependenciesSymb] = dependenciesObj[className]
445
+ if (referencesObj[className]) classMap[referencesSymb] = referencesObj[className]
446
+ }
447
+
448
+ for (const classMap of Object.values(classMapsObj)) {
449
+ if (!Object.keys(classMap.junctions).length) delete classMap.junctions
450
+ else {
451
+ for (const [propertyName, junctionObj] of Object.entries(classMap.junctions)) {
452
+ const className = junctionObj.className
453
+ const { isArray, optional } = junctionObj
454
+ classMap.junctions[propertyName] = { ...classMapsObj[className] }
455
+ if (isArray) classMap.junctions[propertyName].isArray = true
456
+ if (optional) classMap.junctions[propertyName].optional = true
457
+ }
458
+ }
459
+ }
460
+ }
461
+
462
+ export function createClassMap(tableObj) {
463
+ const tableNames = Object.keys(tableObj)
464
+ const ormMapsObj = {}
465
+ const dependenciesObj = {}
466
+ const referencesObj = {}
467
+
468
+ for (const name of tableNames) {
469
+ const table = tableObj[name]
470
+ const junctionMap = {}
471
+ const map = {
472
+ columns: {},
473
+ junctions: junctionMap,
474
+ className: snake2Pascal(table.name)
475
+ }
476
+
477
+ ormMapsObj[map.className] = map
478
+
479
+ if (table.parent) map.parent = snake2Pascal(table.parent.name)
480
+
481
+ for (const columnObj of Object.values(table.columns)) {
482
+ const columnName = columnObj.name
483
+ if (columnName === "id") {
484
+ map.columns.id = { type: columnObj.type }
485
+ continue
486
+ }
487
+ const { type, isArray, nullable } = columnObj
488
+
489
+ if (columnObj.relational) {
490
+ junctionMap[columnName] = {
491
+ className: type,
492
+ isArray: isArray,
493
+ optional: nullable
494
+ }
495
+
496
+ if (!isArray && !nullable) {
497
+ const targetTable = dependenciesObj[type] ??= {}
498
+ const dependantKeyArr = targetTable[map.className] ??= []
499
+ dependantKeyArr.push(columnName)
500
+ }
501
+ else {
502
+ const targetTable = referencesObj[type] ??= {}
503
+ const dependantKeyArr = targetTable[map.className] ??= []
504
+ dependantKeyArr.push(columnName)
505
+ }
506
+ }
507
+ else {
508
+ /**@type {any}*/ const { name, nullable, ...mapColumnObj } = columnObj
509
+ if (nullable) mapColumnObj.optional = true
510
+ if (mapColumnObj.type === `integer`) mapColumnObj.type = `number`
511
+ map.columns[columnName] = mapColumnObj
512
+ }
513
+ }
514
+ }
515
+ linkClassMap(ormMapsObj, dependenciesObj, referencesObj)
516
+ OrmStore.store.classWikiDict = ormMapsObj
517
+ }
518
+
519
+ export function createTableObject(branchArr) {
520
+ let tableObj = {}
521
+ for (const branch of branchArr) {
522
+ if (branch.parent) {
523
+ const newBranch = inheritColumns(branch)
524
+ if (newBranch) branchArr.push(newBranch)
525
+ tableObj[branch.name] = branch
526
+ }
527
+ }
528
+ return tableObj
529
+ }
530
+
531
+ export function createJunctionColumnContext(tablesDict) {
532
+ for (const tableObj of Object.values(tablesDict)) {
533
+ const columns = tableObj.columns
534
+ for (const columnObj of columns) {
535
+ const isArray = columnObj.isArray
536
+ if (columnObj.relational) {
537
+ const snakedColumnType = nonSnake2Snake(columnObj.type)
538
+ const joinedTable = tablesDict[snakedColumnType]
539
+ columnObj.thisTableIdUnique = !isArray
540
+ }
541
+ }
542
+ }
543
+ columnArr2ColumnDict(tablesDict)
544
+ }
545
+
546
+
547
+ function formatRelationalColumnObj(tableObj, columnObj, tablesDict, sqlClient) {
548
+ const newJunctionTable = {
549
+ name: `${tableObj.name}___${nonSnake2Snake(columnObj.name)}_jt`,
550
+ columns: {}
551
+ }
552
+ const joiningIdTypeInDb = js2SqlTyping(sqlClient, tableObj.columns.id.type, true)
553
+ let newJunctionTableColumn = {
554
+ type: joiningIdTypeInDb,
555
+ unique: columnObj.thisTableIdUnique,
556
+ refTable: tableObj.name
557
+ }
558
+ newJunctionTable.columns.joining = newJunctionTableColumn
559
+
560
+ const joinedTableName = columnObj.type
561
+ const joinedTable = tablesDict[joinedTableName] || tablesDict[nonSnake2Snake(joinedTableName)]
562
+ const joinedIdTypeInDb = js2SqlTyping(sqlClient, joinedTable.columns.id.type, true)
563
+
564
+ newJunctionTableColumn = {
565
+ type: joinedIdTypeInDb,
566
+ unique: false,
567
+ refTable: nonSnake2Snake(joinedTableName)
568
+ }
569
+ newJunctionTable.columns.joined = newJunctionTableColumn
570
+ return newJunctionTable
571
+ }
572
+
573
+
574
+ export function formatForCreation(tablesDict) {
575
+ /**@type {TABLE[]}*/ const formattedTables = []
576
+ const alterTableArr = []
577
+ const sqlClient = OrmStore.store.sqlClient
578
+
579
+ for (const tableObj of Object.values(tablesDict)) {
580
+ if (tableObj.alreadyExists) {
581
+ if (!tableObj.newColumns) continue
582
+ alterTableArr.push(tableObj)
583
+ continue
584
+ }
585
+
586
+ /**@type {TABLE}*/ const formattedTable = {
587
+ name: tableObj.name,
588
+ columns: {},
589
+ junctions: []
590
+ }
591
+
592
+ if (tableObj.parent && tableObj.parent.name !== 'entity') {
593
+ let currentParent = tableObj.parent
594
+ while (currentParent.abstract && currentParent.parent) currentParent = currentParent.parent
595
+ if (currentParent.name !== 'entity') formattedTable.parent = tableObj.parent.name
596
+ }
597
+
598
+ for (const columnObj of Object.values(tableObj.columns)) {
599
+ if (columnObj.relational) formattedTable.junctions?.push(formatRelationalColumnObj(tableObj, columnObj, tablesDict, sqlClient))
600
+ else {
601
+ const { name, ...rest } = columnObj
602
+ formattedTable.columns[`${name}`] = rest
603
+ }
604
+ }
605
+ formattedTables.push(formattedTable)
606
+ }
607
+ return [formattedTables, alterTableArr]
608
+ }
609
+ export function produceTableCreationQuery(/**@type {TABLE}*/ table, /**@type {boolean}*/ isJunction = false) {
610
+ let query = `CREATE TABLE ${table.name} (\n`
611
+
612
+ if (!isJunction) {
613
+ const primaryKeyType = table.columns.id.type
614
+ query += ` id ${primaryKeyType} PRIMARY KEY,\n`
615
+
616
+ const columnEntries = Object.entries(table.columns).filter(column => column[0] !== "id")
617
+ query += columnEntries2QueryStr(columnEntries)
618
+
619
+ if (table.parent) query += `FOREIGN KEY (id) REFERENCES ${table.parent}(id) ON DELETE CASCADE \n);`
620
+ else query = query.slice(0, -3) + `);`
621
+ }
622
+ else {
623
+ const columns = Object.entries(table.columns)
624
+
625
+ const baseColumn = {
626
+ type: columns[0][1].type,
627
+ unique: columns[0][1].unique,
628
+ ref: columns[0][1][`refTable`]
629
+ }
630
+ const referencedColumn = {
631
+ type: columns[1][1].type,
632
+ unique: columns[1][1].unique,
633
+ ref: columns[1][1][`refTable`]
634
+ }
635
+ query += ` joining_id ${baseColumn.type}`
636
+
637
+ if (baseColumn.unique) {
638
+ query += ` PRIMARY KEY`
639
+ query += ` REFERENCES ${baseColumn.ref}(id) ON DELETE CASCADE, \n`
640
+ query += `joined_id ${referencedColumn.type} NOT NULL REFERENCES ${referencedColumn.ref}(id) ON DELETE CASCADE`
641
+ }
642
+ else {
643
+ query += ` NOT NULL REFERENCES ${baseColumn.ref}(id) ON DELETE CASCADE, \n`
644
+ query += `joined_id ${referencedColumn.type} NOT NULL REFERENCES ${referencedColumn.ref}(id) ON DELETE CASCADE, \n`
645
+ query += `PRIMARY KEY (joining_id, joined_id)`
646
+ }
647
+ query += `\n);`
648
+ }
649
+ return query
650
+ }
651
+
652
+ export function columnEntries2QueryStr(columnEntries) {
653
+ let queryStr = ``
654
+ const client = OrmStore.store.sqlClient
655
+
656
+ if (client === "postgresql") {
657
+ for (const column of columnEntries) {
658
+ const columnName = nonSnake2Snake(column[0])
659
+
660
+ const { nullable, unique, type, defaultValue } = column[1]
661
+ queryStr += ` ${columnName} ${type}`
662
+ queryStr += nullable ? `` : ` NOT NULL`
663
+ queryStr += unique ? ` UNIQUE` : ``
664
+
665
+ //TODO BELOW, DEFAULT VALUE IS PROBABLY REDUNDANT? even though it might not be...lets you
666
+ // set values without using args with default values in constructor, which can limit freedom because of args order
667
+ // if we keep this, need to account for this during row creation save
668
+ // query +=
669
+ // defaultValue !== undefined
670
+ // ? ` DEFAULT ${typeof defaultValue === `object`
671
+ // ? `'${JSON.stringify(defaultValue)}'`
672
+ // : defaultValue
673
+ // }`
674
+ // : ``
675
+
676
+ queryStr += `, \n`
677
+ }
678
+ }
679
+ else {
680
+ for (const column of columnEntries) {
681
+ const columnName = nonSnake2Snake(column[0])
682
+ const { nullable, unique, type, defaultValue } = column[1]
683
+
684
+ if (type.endsWith('[]')) queryStr += ` ${columnName} TEXT`
685
+ else queryStr += ` ${columnName} ${type}`
686
+
687
+ queryStr += nullable ? `` : ` NOT NULL`
688
+ queryStr += unique ? ` UNIQUE` : ``
689
+ queryStr += `, \n`
690
+ }
691
+ }
692
+ return queryStr
693
+ }
694
+
695
+ export function sqlTypeTableObj(tableObj, sqlClient) {
696
+ //if (tableObj.parent === `entity`) delete tableObj.parent
697
+ const columnObjectsArr = Object.values(tableObj.columns)
698
+
699
+ if (sqlClient === `postgresql`) {
700
+ for (const columnObj of columnObjectsArr) {
701
+ if (columnObj.isArray) {
702
+ if (columnObj.type === `object`) columnObj.type = `JSONB`
703
+ else columnObj.type = js2SqlTyping(sqlClient, columnObj.type) + `[]`
704
+ }
705
+ else columnObj.type = js2SqlTyping(sqlClient, columnObj.type)
706
+ }
707
+ }
708
+ else {
709
+ for (const columnObj of columnObjectsArr) {
710
+ if (columnObj.isArray) columnObj.type = `TEXT`
711
+ else columnObj.type = js2SqlTyping(sqlClient, columnObj.type)
712
+ }
713
+ }
714
+ }
715
+
716
+ export function generateTableCreationQueryObject(formattedTables) {
717
+ const sqlClient = OrmStore.store.sqlClient
718
+ for (const tableObj of Object.values(formattedTables)) {
719
+ sqlTypeTableObj(tableObj, sqlClient)
720
+ if (floatColumnTypes.includes(tableObj.columns.id.type)) tableObj.columns.id.type = 'INTEGER'
721
+ }
722
+
723
+ const rootTables = formattedTables.filter(table => !table.parent)
724
+ const childrenTables = formattedTables.filter(table => table.parent)
725
+
726
+ const tableQueryObject = {}
727
+ const junctionQueriesArr = []
728
+
729
+ for (const rootTable of rootTables) produceQueryObj(rootTable, tableQueryObject, childrenTables, junctionQueriesArr)
730
+
731
+ return { tableQueryObject, junctionQueriesArr }
732
+ }
733
+
734
+ export async function sendQueryFromQueryObj(queryObj, queryFunc) {
735
+ const { query, children } = queryObj
736
+ await queryFunc(query)
737
+
738
+ if (children) {
739
+ const childrenQueryObjects = Object.values(children)
740
+ for (const queryObject of childrenQueryObjects) await sendQueryFromQueryObj(queryObject, queryFunc)
741
+ }
742
+ }
743
+
744
+ export async function sendTableCreationQueries(tableCreationObj) {
745
+ const { dbConnection, sqlClient } = OrmStore.store
746
+ let queryFunc
747
+ if (sqlClient === "postgresql") queryFunc = dbConnection.query.bind(dbConnection)
748
+ else queryFunc = (query) => dbConnection.exec(query)
749
+
750
+ const queryFuncWithTryCatch = async (query) => {
751
+ try {
752
+ await queryFunc(query)
753
+ }
754
+ catch (e) {
755
+ coloredBackgroundConsoleLog(`Error while creating tables. ${e}\n`, `failure`)
756
+ }
757
+ }
758
+
759
+ const tableQueryObjectsEntries = Object.entries(tableCreationObj.tableQueryObject)
760
+ coloredBackgroundConsoleLog(`Updating database schema...\n`, `success`)
761
+ for (const [tableName, queryObj] of tableQueryObjectsEntries) await sendQueryFromQueryObj(queryObj, queryFuncWithTryCatch)
762
+ for (const query of tableCreationObj.junctionQueriesArr) await queryFuncWithTryCatch(query)
763
+ }
764
+
765
+
766
+ export function produceQueryObj(rootTable, tableQueryObject, childrenTables, junctionQueriesArr) {
767
+ const query = produceTableCreationQuery(rootTable)
768
+
769
+ const junctions = rootTable.junctions
770
+ for (const junctionTable of junctions) junctionQueriesArr.push(produceTableCreationQuery(junctionTable, true))
771
+
772
+ const rootTableQueryObj = tableQueryObject[rootTable.name] = { query }
773
+ const children = childrenTables.filter(table => table.parent === rootTable.name)
774
+ childrenTables = childrenTables.filter(table => table.parent !== rootTable.name)
775
+ if (children.length) {
776
+ const tableQueryObject = rootTableQueryObj[`children`] = {}
777
+ for (const childTable of children) produceQueryObj(childTable, tableQueryObject, childrenTables, junctionQueriesArr)
778
+ }
779
+ }
780
+
781
+
782
+ export function handleSpecialSettingId(tablesDict) {
783
+ for (const classObj of Object.values(tablesDict)) {
784
+ if (classObj.parent === 'entity' && classObj.specialSettings && classObj.specialSettings.idType) {
785
+ const settingsObj = classObj.specialSettings
786
+ const specialIdType = settingsObj.idType
787
+ let idObj = idType2IdColumnObj(specialIdType)
788
+ classObj.columns.id = idObj
789
+ // if (classObj.children) changeIdColumnOnChildren(idObj, tablesDict, classObj.children)
790
+ }
791
+ }
792
+ }
793
+
794
+ // export function changeIdColumnOnChildren(idObj, classesObj, childrenNameArr) {
795
+ // const nextChildrenNameArr = []
796
+ // for (const childName of childrenNameArr) {
797
+ // const childObj = classesObj[childName]
798
+ // childObj.columns.id = idObj
799
+ // if (childObj.children) nextChildrenNameArr.push(...childObj.children)
800
+ // }
801
+ // if (nextChildrenNameArr.length) changeIdColumnOnChildren(idObj, classesObj, nextChildrenNameArr)
802
+ // }
803
+
804
+ export function idType2IdColumnObj(idType) {
805
+ let idColumnObj
806
+ if (idType === "UUID") {
807
+ idColumnObj = { name: "id", type: "string" }
808
+ }
809
+ else if (idType === "INT") {
810
+ idColumnObj = { name: "id", type: "number" }
811
+ }
812
+ else if (idType === "BIGINT") {
813
+ idColumnObj = { name: "id", type: "bigint" }
814
+ }
815
+ else throw new Error(`\n'${idType}' is not a valid primary key type.`)
816
+ return idColumnObj
817
+ }
818
+
819
+ export function columnArr2ColumnDict(tablesObj) {
820
+ for (const tableObj of Object.values(tablesObj)) {
821
+ const columnsDictObj = {}
822
+ for (const columnObj of tableObj.columns) {
823
+ columnsDictObj[columnObj.name] = columnObj
824
+ }
825
+ tableObj.columns = columnsDictObj
826
+ }
827
+ }
828
+
829
+ // export function filterTableArray(tableArray) {
830
+ // let filteredArr = []
831
+ // for (let table of tableArray) {
832
+ // let ancestorOfEntity = false
833
+ // let currentTable = table
834
+ // while (currentTable.parent) {
835
+ // if (currentTable.parent.name === 'entity') ancestorOfEntity = true
836
+ // currentTable = currentTable.parent
837
+ // }
838
+ // if (ancestorOfEntity) filteredArr.push(table)
839
+ // }
840
+ // return filteredArr
841
+ // }
842
+
843
+
844
+ export async function alterTables(tables2alterArr) {
845
+ if (!tables2alterArr.length) return
846
+
847
+ const { sqlClient, dbConnection, classWikiDict } = OrmStore.store
848
+ const queryFunc = sqlClient === `postgresql`
849
+ ? async (alterStatements, junctionStatements) => {
850
+ try {
851
+ if (alterStatements.length) await dbConnection.query(alterStatements.join(`, `))
852
+ if (junctionStatements.length) {
853
+ await dbConnection.query('BEGIN;')
854
+ for (const statement of junctionStatements) await dbConnection.query(statement)
855
+ await dbConnection.query('COMMIT;')
856
+ }
857
+ }
858
+ catch (e) {
859
+ if (junctionStatements.length) await dbConnection.query('ROLLBACK;')
860
+ coloredBackgroundConsoleLog(e, "failure")
861
+ }
862
+ }
863
+ : (alterStatements, junctionStatements) => {
864
+ const statements = [...alterStatements, ...junctionStatements]
865
+ try {
866
+ dbConnection.exec('BEGIN;')
867
+ for (const statement of statements) {
868
+ dbConnection.exec(statement)
869
+ }
870
+ dbConnection.exec('COMMIT;')
871
+ }
872
+ catch (e) {
873
+ dbConnection.exec('ROLLBACK;')
874
+ coloredBackgroundConsoleLog(e, "failure")
875
+ }
876
+ }
877
+
878
+ let alterTableStr
879
+ const alterStatements = []
880
+ const newJunctionStatements = []
881
+
882
+ for (const tableObj of tables2alterArr) {
883
+ alterTableStr = `ALTER TABLE ${tableObj.name} `
884
+ const idColumn = tableObj.columns.id
885
+ tableObj.columns = structuredClone(tableObj.newColumns)
886
+ sqlTypeTableObj(tableObj, sqlClient)
887
+
888
+ tableObj.columns.id = idColumn
889
+
890
+ for (const [columnName, columnObj] of Object.entries(tableObj.newColumns)) {
891
+ const { type, nullable, isArray, relational } = columnObj
892
+ let statement
893
+ if (relational) {
894
+ const junctionTableObj = formatRelationalColumnObj(tableObj, columnObj, classWikiDict, sqlClient)
895
+ statement = produceTableCreationQuery(junctionTableObj, true)
896
+ newJunctionStatements.push(statement)
897
+ }
898
+ else {
899
+ statement = `ADD COLUMN ${nonSnake2Snake(columnName)} ${tableObj.columns[columnName].type} `
900
+ if (!nullable) statement += `NOT NULL DEFAULT ${type2DefaultValue(type, isArray, sqlClient)}`
901
+ alterStatements.push(alterTableStr + statement)
902
+ }
903
+ }
904
+ }
905
+
906
+ await queryFunc(alterStatements, newJunctionStatements)
907
+ }
908
+
909
+ function type2DefaultValue(type, isArray, sqlClient) {
910
+ //need to get correct types from tableObj.newColumns
911
+ if (sqlClient === `postgresql`) {
912
+ if (isArray) {
913
+ const typeCast = js2SqlTyping(sqlClient, type)
914
+ return `ARRAY[]::${typeCast}[]`
915
+ }
916
+ else if (type === `string`) return `''`
917
+ else if (type === `boolean`) return false
918
+ else if (type === `number` || type === `integer`) return 0
919
+ else if (type === `bigint`) return `0`
920
+ else if (type === `object`) return `'{}'::JSONB`
921
+ }
922
+
923
+ if (isArray) return `'[]'`
924
+ else if (type === `string`) return `''`
925
+ else if (type === `boolean`) return 0
926
+ else if (type === `number` || type === `integer`) return 0
927
+ else if (type === `bigint`) return `0`
928
+ else if (type === `object`) return `'{}'`
929
+ }