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,55 @@
1
+ import { OrmStore } from "../misc/ormStore.js"
2
+ import { handleRelationalChanges, handleUpserts, organizeChangeObj } from "./save.js"
3
+ import { postgresSaveQuery } from "./sqlClients/postgres.js"
4
+ import { sqliteSaveQuery } from "./sqlClients/sqlite.js"
5
+ import { setImmediate } from "node:timers/promises";
6
+
7
+ export class ChangeLogger {
8
+ static scheduledFlush = false
9
+ static currentlySaving = false
10
+ static flushChanges() {
11
+ const dbChangesObj = OrmStore.store.dbChangesObj
12
+ if (!Object.keys(dbChangesObj).length || this.scheduledFlush) return
13
+ this.scheduledFlush = true
14
+ const func = async () => await ChangeLogger.save()
15
+ //queueMicrotask(func)
16
+ setImmediate(func)
17
+ }
18
+
19
+
20
+ static async save() {
21
+ const dbChanges = OrmStore.store.dbChangesObj
22
+ if (!Object.keys(dbChanges).length || ChangeLogger.currentlySaving) return
23
+ // ChangeLogger.currentlySaving = true
24
+
25
+ let paramIndex = 1
26
+ const { sqlClient, dbConnection } = OrmStore.store
27
+ const deletedInstancesArr = dbChanges.deletedInstancesArr
28
+ const deletedUncalledRelationsArr = dbChanges.deletedUncalledRelationsArr //this has to fire first
29
+ if (deletedInstancesArr) delete dbChanges.deletedInstancesArr
30
+ if (deletedUncalledRelationsArr) delete dbChanges.deletedUncalledRelationsArr
31
+
32
+ const organizedChangeObj = {}
33
+ organizeChangeObj(dbChanges, organizedChangeObj, sqlClient)
34
+
35
+ const entitiesChangeObj = organizedChangeObj.tables ?? {}
36
+ const junctionTablesChangeObj = organizedChangeObj.junctions ?? {}
37
+
38
+ const classesQueryObj = {}
39
+ const junctionsQueryObj = {}
40
+
41
+ for (const [tableName, classChangesObj] of Object.entries(entitiesChangeObj)) {
42
+ if (!Object.keys(classChangesObj).length) continue
43
+ paramIndex = handleUpserts(tableName, classChangesObj, classesQueryObj, paramIndex, sqlClient)
44
+ }
45
+
46
+ for (const [tableName, junctionChangesObj] of Object.entries(junctionTablesChangeObj)) {
47
+ if (!Object.keys(junctionChangesObj).length) continue
48
+ paramIndex = handleRelationalChanges(tableName, junctionChangesObj, junctionsQueryObj, paramIndex, sqlClient)
49
+ }
50
+
51
+ if (sqlClient === "postgresql") await postgresSaveQuery(deletedUncalledRelationsArr, classesQueryObj, junctionsQueryObj, deletedInstancesArr, paramIndex, dbConnection)
52
+ else sqliteSaveQuery(deletedUncalledRelationsArr, classesQueryObj, junctionsQueryObj, deletedInstancesArr, dbConnection)
53
+ }
54
+ }
55
+
@@ -0,0 +1,237 @@
1
+ import { newEntityInstanceSymb } from "../misc/constants.js"
2
+ import { coloredBackgroundConsoleLog, getPropertyClassification, jsValue2SqliteValue, nonSnake2Snake } from "../misc/miscFunctions.js"
3
+ import { OrmStore } from "../misc/ormStore.js"
4
+ import { ChangeLogger } from "./changeLogger.js"
5
+ import { junctionTableRemovalPostgres } from "./sqlClients/postgres.js"
6
+ import { junctionTableRemovalSqlite } from "./sqlClients/sqlite.js"
7
+
8
+ const idTypeSymb = Symbol(`idType`)
9
+
10
+ export function successfullSaveOperation() {
11
+ coloredBackgroundConsoleLog(`Save ran successfully.\n`, `success`)
12
+ OrmStore.clearDbChanges()
13
+ ChangeLogger.scheduledFlush = false
14
+ ChangeLogger.currentlySaving = false
15
+ }
16
+
17
+ function expandBuffer(arr, lengthAdded) {
18
+ arr.length += lengthAdded
19
+ return arr
20
+ }
21
+
22
+ function passEntityColumnsToAncestorMaps(id, entityInstanceChangeObj, classWiki, cteMap, client) {
23
+ const isNewEntityInstance = entityInstanceChangeObj[newEntityInstanceSymb] ? true : false
24
+ cteMap.tables ??= {}
25
+ const baseClassCteMap = cteMap.tables[classWiki.className] ??= {}
26
+ baseClassCteMap[id] = { id }
27
+ let currentWiki = classWiki
28
+ while (currentWiki.parent) {
29
+ const classCteMap = cteMap.tables[currentWiki.parent.className] ??= {}
30
+ classCteMap[id] ??= { id }
31
+ classCteMap[id][newEntityInstanceSymb] = isNewEntityInstance
32
+ currentWiki = currentWiki.parent ?? currentWiki
33
+ }
34
+ cteMap.tables[currentWiki.className][id].updatedAt = client === "postgresql" ? new Date() : new Date().toISOString()
35
+ }
36
+
37
+ export function handleRelationalChanges(tableName, tableChangesObj, queryObj, paramIndex, sqlClient) {
38
+ /**@type {any}*/ const target = queryObj[tableName] = {}
39
+ const categorizedIds = { added: {}, removed: {} }
40
+ const entries = Object.entries(tableChangesObj)
41
+ const idTypeArr = tableChangesObj[idTypeSymb]
42
+
43
+ for (const [baseEntityId, addedAndRemovedIds] of entries) {
44
+ if (addedAndRemovedIds.added) {
45
+ categorizedIds.added[baseEntityId] = []
46
+ for (const addedJoinId of addedAndRemovedIds.added) categorizedIds.added[baseEntityId].push(addedJoinId)
47
+ }
48
+ if (addedAndRemovedIds.removed) {
49
+ categorizedIds.removed[baseEntityId] = []
50
+ for (const removedJoinId of addedAndRemovedIds.removed) categorizedIds.removed[baseEntityId].push(removedJoinId)
51
+ }
52
+ }
53
+
54
+ let addedIds = Object.entries(categorizedIds.added)
55
+ let removedIds = Object.entries(categorizedIds.removed)
56
+
57
+ addedIds = addedIds.map(([joiningId, joinedIdArr]) => [typecastStringId(joiningId, idTypeArr[0]), joinedIdArr])
58
+ removedIds = removedIds.map(([joiningId, joinedIdArr]) => [typecastStringId(joiningId, idTypeArr[0]), joinedIdArr])
59
+
60
+ if (addedIds.length) [target.newRelationsObj, paramIndex] = junctionTableInsertion(addedIds, tableName, paramIndex, sqlClient)
61
+ if (removedIds.length) [target.deletedRelationsObj, paramIndex] = sqlClient === `postgresql`
62
+ ? junctionTableRemovalPostgres(removedIds, tableName, paramIndex, idTypeArr)
63
+ : junctionTableRemovalSqlite(removedIds, tableName)
64
+ return paramIndex
65
+ }
66
+
67
+ function junctionTableInsertion(addedIds, tableName, paramIndex, sqlClient) {
68
+ const snakedTableName = nonSnake2Snake(tableName)
69
+ let queryStr = `INSERT INTO ${snakedTableName} (joining_id, joined_id) VALUES `
70
+ const params = []
71
+ //const [joiningIdTypeCast, joinedIdTypeCast] = [getPostgresIdTypeCasting(idTypeArr[0]), getPostgresIdTypeCasting(idTypeArr[1])]
72
+ for (const baseAndJoinedIds of addedIds) {
73
+ const baseId = baseAndJoinedIds[0]
74
+ const joinedIds = baseAndJoinedIds[1]
75
+ while (joinedIds.length) {
76
+ queryStr += sqlClient === "postgresql" ? `($${paramIndex++}, $${paramIndex++}), ` : `(?, ?), `
77
+ params.push(baseId, joinedIds.pop())
78
+ }
79
+ }
80
+ queryStr = queryStr.slice(0, -2)
81
+ if (sqlClient === "postgresql") queryStr += ` RETURNING 1`
82
+
83
+ const returnedJunctionObj = { queryStr, params }
84
+ return [returnedJunctionObj, paramIndex]
85
+ }
86
+
87
+
88
+ export function handleUpserts(tableName, classChangesObj, queryObj, paramIndex, sqlClient) {
89
+ const classInstances = Object.entries(classChangesObj)
90
+ const inserts = []
91
+ const updates = []
92
+ queryObj[tableName] ??= {}
93
+ while (classInstances.length) {
94
+ //@ts-ignore
95
+ const [instanceId, instance] = classInstances.pop()
96
+ if (Object.keys(instance).length === 1) continue
97
+ if (instance[newEntityInstanceSymb]) inserts.push(instance)
98
+ else updates.push(instance)
99
+ }
100
+
101
+ if (inserts.length) paramIndex = insertNewRows(inserts, tableName, queryObj, paramIndex, sqlClient)
102
+ if (updates.length) paramIndex = updateRows(updates, tableName, queryObj, paramIndex, sqlClient)
103
+ return paramIndex
104
+ }
105
+
106
+ export function typecastStringId(instanceId, idType) {
107
+ if (idType === `number`) return parseInt(instanceId, 10)
108
+ return instanceId
109
+ }
110
+
111
+ function insertNewRows(newRows, tableName, queryObj, paramIndex, client) {
112
+ /**@type {any}*/ const target = queryObj[tableName].insert = { queryStr: ``, params: [] }
113
+ const snakedTableName = nonSnake2Snake(tableName)
114
+ const classWiki = OrmStore.store.classWikiDict[tableName]
115
+ const columns = Object.keys(classWiki.columns)
116
+
117
+ if (classWiki.parent) queryObj[tableName].parent = classWiki.parent.className
118
+
119
+ target.params = expandBuffer(target.params, newRows.length * columns.length)
120
+ let i = 0
121
+ let queryStr = `INSERT INTO ${snakedTableName} (${columns.map(column => nonSnake2Snake(column)).join(', ')}) VALUES `
122
+
123
+ for (const instance of newRows) {
124
+ if (client === "postgresql") queryStr += `(${columns.map(column => `$${paramIndex++}`).join(', ')}), \n`
125
+ else queryStr += `(${columns.map(column => `?`).join(', ')}), \n`
126
+
127
+ for (const column of columns) target.params[i++] = instance[column]
128
+ }
129
+
130
+ if (client === "postgresql") target.queryStr = queryStr.slice(0, -3) + ` RETURNING 1`
131
+ else target.queryStr = queryStr.slice(0, -3)
132
+ return paramIndex
133
+ }
134
+
135
+ function updateRows(updatedRows, tableName, queryObj, paramIndex, client) {
136
+ /**@type {any}*/ const target = queryObj[tableName].update = { queryStrArr: [], params2dArr: [] }
137
+ const snakedTableName = nonSnake2Snake(tableName)
138
+ const idType = OrmStore.getClassWiki(tableName).columns.id.type
139
+
140
+ for (const row of updatedRows) {
141
+ let rowId = row.id
142
+ delete row.id
143
+ const updatedColumns = Object.entries(row)
144
+ //if (!updatedColumns.length) continue
145
+ rowId = typecastStringId(rowId, idType)
146
+ let queryStr = ``
147
+ let params = []
148
+
149
+ queryStr += `UPDATE ${snakedTableName} SET `
150
+ for (const [columnName, val] of updatedColumns) {
151
+ if (client === "postgresql") queryStr += `${nonSnake2Snake(columnName)} = $${paramIndex++}, `
152
+ else queryStr += `${nonSnake2Snake(columnName)} = ?, `
153
+
154
+ params.push(val)
155
+ }
156
+ if (client === "postgresql") queryStr = queryStr.slice(0, -2) + ` WHERE id = $${paramIndex++} RETURNING 1`
157
+ else queryStr = queryStr.slice(0, -2) + ` WHERE id = ?`
158
+
159
+ params.push(rowId)
160
+ target.queryStrArr.push(queryStr)
161
+ target.params2dArr.push(params)
162
+ }
163
+ return paramIndex
164
+ }
165
+
166
+ export function organizeChangeObj(dbChanges, cteMap, client) {
167
+ const classNames = Object.keys(dbChanges)
168
+
169
+ for (const className of classNames) {
170
+ const tableChangeObj = dbChanges[className]
171
+ const entityChangeObjects = Object.entries(tableChangeObj)
172
+
173
+ for (const [instanceId, entityInstanceChangeObj] of entityChangeObjects) {
174
+
175
+ const classWiki = OrmStore.store.classWikiDict[className]
176
+ let properties = Object.keys(entityInstanceChangeObj)
177
+
178
+ if (classWiki.parent) {
179
+ passEntityColumnsToAncestorMaps(instanceId, entityInstanceChangeObj, classWiki, cteMap, client)
180
+ properties = properties.filter(prop => prop !== "id" && prop !== "updatedAt")
181
+ }
182
+
183
+ for (const property of properties) {
184
+ const [classification, columnType, mapWithProp] = getPropertyClassification(property, classWiki)
185
+
186
+ if (classification === "Join" || classification === "ParentJoin") {
187
+ const added = entityInstanceChangeObj[property].added
188
+ const removed = entityInstanceChangeObj[property].removed
189
+ if (!added.length && !removed.length) continue
190
+
191
+ const junctionTableName = `${nonSnake2Snake(mapWithProp.className)}___${nonSnake2Snake(property)}_jt`
192
+
193
+ cteMap.junctions ??= {}
194
+ const tableCteMap = cteMap.junctions[junctionTableName] ??= {}
195
+ tableCteMap[idTypeSymb] = [mapWithProp.columns.id.type, columnType.columns.id.type]
196
+
197
+ if (added.length) {
198
+ tableCteMap[instanceId] ??= {}
199
+ tableCteMap[instanceId].added = entityInstanceChangeObj[property].added
200
+ }
201
+ if (removed.length) {
202
+ tableCteMap[instanceId] ??= {}
203
+ tableCteMap[instanceId].removed = entityInstanceChangeObj[property].removed
204
+ }
205
+ }
206
+ else {
207
+ const tableName = mapWithProp.className
208
+ cteMap.tables ??= {}
209
+ const tableCteMap = cteMap.tables[tableName] ??= {}
210
+ const entityInstanceMap = tableCteMap[instanceId] ??= {}
211
+ //tableCteMap[idTypeSymb] = [mapWithProp.columns.id.type, columnType.type]
212
+
213
+ if (client === "postgresql") entityInstanceMap[property] = entityInstanceChangeObj[property]
214
+ else {
215
+ const value = entityInstanceChangeObj[property]
216
+ entityInstanceMap[property] = jsValue2SqliteValue(value)
217
+ }
218
+ entityInstanceMap[newEntityInstanceSymb] = entityInstanceChangeObj[newEntityInstanceSymb] ? true : false
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
224
+
225
+
226
+ // function getPostgresTypeCasting(columnObj) {
227
+ // const { type, isArray } = columnObj
228
+ // let returnedType
229
+ // if (type === `string`) returnedType = `::text`
230
+ // else if (type === `number`) returnedType = `::int`
231
+ // else if (type === `bigint`) returnedType = `::bigint`
232
+ // else if (type === `Date`) returnedType = `::timestamptz`
233
+ // else if (type === `boolean`) returnedType = `::bool`
234
+ // else if (type === `object`) returnedType = `::jsonb`
235
+ // if (isArray && type !== `object`) returnedType += `[]`
236
+ // return returnedType
237
+ // }
@@ -0,0 +1,97 @@
1
+ import { coloredBackgroundConsoleLog, nonSnake2Snake } from "../../misc/miscFunctions.js"
2
+ import { successfullSaveOperation } from "../save.js"
3
+
4
+ export async function postgresSaveQuery(deletedUncalledRelationsArr, classesQueryObj, junctionsQueryObj, deletedInstancesArr, paramIndex, dbConnection) {
5
+ let i = 0
6
+ let finalString = 'WITH'
7
+ const finalParams = []
8
+ paramIndex = 1
9
+
10
+ let queryFunc = dbConnection.query.bind(dbConnection)
11
+ let queryFuncWithTryCatch = async (query, params, forDeletedUncalledRelations = false) => {
12
+ try {
13
+ await queryFunc(query, params)
14
+ if (!forDeletedUncalledRelations) successfullSaveOperation()
15
+ }
16
+ catch (e) {
17
+ coloredBackgroundConsoleLog(`Database save failed. ${e}\n`, `failure`)
18
+ }
19
+ }
20
+
21
+ if (deletedUncalledRelationsArr) {
22
+ const paramsArr = []
23
+ let finalString = 'WITH'
24
+ for (const [tableName, { idType, params }] of Object.entries(deletedUncalledRelationsArr)) {
25
+ const queryStr = `DELETE FROM ${tableName} WHERE joining_id = ANY($${paramIndex++}::${idType}[])`
26
+ finalString += ` cte${i++} AS (` + queryStr + `), `
27
+ paramsArr.push(params)
28
+ }
29
+ await queryFuncWithTryCatch(finalString.slice(0, -2) + ` SELECT 1`, paramsArr, true)
30
+ }
31
+
32
+ for (const upsertObj of Object.values(classesQueryObj)) {
33
+ if (upsertObj.insert) {
34
+ finalString += ` cte${i++} AS (` + upsertObj.insert.queryStr + `), `
35
+ finalParams.push(...upsertObj.insert.params)
36
+ }
37
+ if (upsertObj.update) {
38
+ finalString += upsertObj.update.queryStrArr.map((queryStr) => ` cte${i++} AS (${queryStr})`).join(', ') + `, `
39
+ finalParams.push(...upsertObj.update.params2dArr.flat())
40
+ }
41
+ }
42
+
43
+ for (const junctionObj of Object.values(junctionsQueryObj)) {
44
+ if (junctionObj.newRelationsObj) {
45
+ finalString += ` cte${i++} AS (` + junctionObj.newRelationsObj.queryStr + `), `
46
+ finalParams.push(...junctionObj.newRelationsObj.params)
47
+ }
48
+
49
+ if (junctionObj.deletedRelationsObj) {
50
+ finalString += ` cte${i++} AS (` + junctionObj.deletedRelationsObj.queryStr + `), `
51
+ finalParams.push(...junctionObj.deletedRelationsObj.params)
52
+ }
53
+ }
54
+
55
+ if (deletedInstancesArr) {
56
+ paramIndex = finalParams.length + 1
57
+ for (const [tableName, param] of deletedInstancesArr) {
58
+ const queryStr = `DELETE FROM ${tableName} WHERE id = $${paramIndex++}`
59
+ finalString += ` cte${i++} AS (` + queryStr + `), `
60
+ finalParams.push(param)
61
+ }
62
+ }
63
+
64
+ finalString = finalString.slice(0, -2) + ` SELECT 1`
65
+ await queryFuncWithTryCatch(finalString, finalParams)
66
+ }
67
+
68
+
69
+ function getPostgresIdTypeCasting(idType) {
70
+ if (idType === `string`) return `::uuid`
71
+ else if (idType === `number`) return `::int`
72
+ else return `::bigint`
73
+ }
74
+
75
+
76
+ export function junctionTableRemovalPostgres(removedIds, tableName, paramIndex, idTypeArr) {
77
+ const snakedTableName = nonSnake2Snake(tableName)
78
+ let queryStr = ``
79
+ const params = []
80
+
81
+ const [joiningIdTypeCast, joinedIdTypeCast] = [getPostgresIdTypeCasting(idTypeArr[0]), getPostgresIdTypeCasting(idTypeArr[1])]
82
+ queryStr = `DELETE FROM ${snakedTableName} AS jt USING (VALUES`
83
+ for (const idPairings of removedIds) {
84
+ const joiningId = idPairings[0]
85
+ const removedJoinedIds = idPairings[1]
86
+ for (const removedId of removedJoinedIds) {
87
+ queryStr += `($${paramIndex++}${joiningIdTypeCast}, $${paramIndex++}${joinedIdTypeCast}), `
88
+ params.push(joiningId, removedId)
89
+ }
90
+ }
91
+ queryStr = queryStr.slice(0, -2) + `) AS to_delete(joining_id, joined_id) `
92
+ queryStr += `WHERE jt.joining_id = to_delete.joining_id AND jt.joined_id = to_delete.joined_id `
93
+ queryStr += `RETURNING 1`
94
+
95
+ const returnedJunctionObj = { queryStr, params }
96
+ return [returnedJunctionObj, paramIndex]
97
+ }
@@ -0,0 +1,120 @@
1
+ import { coloredBackgroundConsoleLog, nonSnake2Snake } from "../../misc/miscFunctions.js"
2
+ import { successfullSaveOperation } from "../save.js"
3
+
4
+ export function sqliteSaveQuery(deletedUncalledRelationsArr, classesQueryObj, junctionsQueryObj, deletedInstancesArr, dbConnection) {
5
+ const classTableNames = Object.keys(classesQueryObj ?? {})
6
+ const queryFuncWithTryCatch = (queryStr, params) => {
7
+ try {
8
+ const queryFunc = dbConnection.prepare(queryStr)
9
+ queryFunc.run(...params)
10
+ return true
11
+ }
12
+ catch (e) {
13
+ coloredBackgroundConsoleLog(`Database save failed. ${e}\n`, `failure`)
14
+ dbConnection.exec('ROLLBACK;')
15
+ return false
16
+ }
17
+ }
18
+
19
+ let visitedTables = []
20
+ dbConnection.exec('BEGIN;')
21
+
22
+ if (deletedUncalledRelationsArr) {
23
+ for (const [tableName, idArr] of Object.entries(deletedUncalledRelationsArr)) {
24
+ const queryStr = `DELETE FROM ${tableName} WHERE joining_id IN (${idArr.map(id => `?`).join(`, `)})`
25
+ if (!queryFuncWithTryCatch(queryStr, idArr)) return
26
+ }
27
+ }
28
+
29
+ for (const tableName of classTableNames) {
30
+ if (visitedTables.includes(tableName)) continue
31
+ const queryObj = classesQueryObj[tableName]
32
+
33
+ visitedTables = queryObj.parent
34
+ ? sqliteHandleAncestry(tableName, queryObj, visitedTables, classesQueryObj, queryFuncWithTryCatch)
35
+ : queryQueryObj(queryObj, tableName, visitedTables, queryFuncWithTryCatch)
36
+ if (!visitedTables) return
37
+ }
38
+
39
+ const junctionTableQueryObjects = Object.values(junctionsQueryObj ?? {})
40
+ for (const queryObj of junctionTableQueryObjects) {
41
+ if (queryObj.deletedRelationsObj)
42
+ if (!queryFuncWithTryCatch(queryObj.deletedRelationsObj.queryStr, queryObj.deletedRelationsObj.params)) return
43
+
44
+ if (queryObj.newRelationsObj)
45
+ if (!queryFuncWithTryCatch(queryObj.newRelationsObj.queryStr, queryObj.newRelationsObj.params)) return
46
+ }
47
+
48
+ if (deletedInstancesArr) {
49
+ for (const [tableName, param] of deletedInstancesArr) {
50
+ const queryStr = `DELETE FROM ${tableName} WHERE id = ?;`
51
+ if (!queryFuncWithTryCatch(queryStr, [param])) return
52
+ }
53
+ }
54
+
55
+ dbConnection.exec('COMMIT;')
56
+ successfullSaveOperation()
57
+ }
58
+
59
+
60
+ function queryQueryObj(queryObj, tableName, visitedTables, queryFuncWithTryCatch) {
61
+ visitedTables.push(tableName)
62
+ if (queryObj.insert) {
63
+ if (!queryFuncWithTryCatch(queryObj.insert.queryStr, queryObj.insert.params)) return false
64
+ }
65
+
66
+ if (queryObj.update) {
67
+ for (const [index, queryStr] of Object.entries(queryObj.update.queryStrArr))
68
+ if (!queryFuncWithTryCatch(queryStr, queryObj.update.params2dArr[index])) return false
69
+ }
70
+
71
+ return visitedTables
72
+ }
73
+
74
+ function sqliteHandleAncestry(tableName, queryObj, visitedTables, classesQueryObj, queryFuncWithTryCatch) {
75
+ if (visitedTables.includes(queryObj.parent)) {
76
+ const queryRes = queryQueryObj(queryObj, tableName, visitedTables, queryFuncWithTryCatch)
77
+ if (queryRes) {
78
+ visitedTables = queryRes
79
+ return visitedTables
80
+ }
81
+ return false
82
+ }
83
+
84
+ const ancestryArr = [tableName]
85
+ let currentQueryObj = queryObj
86
+ while (currentQueryObj.parent) {
87
+ const parentName = currentQueryObj.parent
88
+ ancestryArr.push(parentName)
89
+ currentQueryObj = classesQueryObj[parentName]
90
+ }
91
+
92
+ while (ancestryArr.length) {
93
+ const ancestorTableName = ancestryArr.pop()
94
+ currentQueryObj = classesQueryObj[ancestorTableName]
95
+ const queryRes = queryQueryObj(currentQueryObj, ancestorTableName, visitedTables, queryFuncWithTryCatch)
96
+ if (queryRes) visitedTables = queryRes
97
+ else return false
98
+ }
99
+ return visitedTables
100
+ }
101
+
102
+
103
+ export function junctionTableRemovalSqlite(removedIds, tableName) {
104
+ const snakedTableName = nonSnake2Snake(tableName)
105
+ let queryStr = ``
106
+ const params = []
107
+
108
+ queryStr = `DELETE FROM ${snakedTableName} WHERE (joining_id, joined_id) IN (`
109
+ for (const idPairings of removedIds) {
110
+ const baseId = idPairings[0]
111
+ const nonBaseIds = idPairings[1]
112
+ for (const removedId of nonBaseIds) {
113
+ queryStr += `(?, ?), `
114
+ params.push(baseId, removedId)
115
+ }
116
+ }
117
+ queryStr = queryStr.slice(0, -2) + `)`
118
+ const returnedJunctionObj = { queryStr, params }
119
+ return [returnedJunctionObj, undefined]
120
+ }
@@ -0,0 +1,20 @@
1
+ export function throwDeletionErr(className, id4Deletion) {
2
+ throw new Error(`Unable to delete ${className} with id of ${id4Deletion} due to potential relational dependencies. Use the async method 'getDependents' to get relational dependency data and decouple it from the deleted instance for the deletion to work.`)
3
+ }
4
+
5
+ export function throwImproperDecouplingErr(className, id4Deletion) {
6
+ throw new Error(`Unable to delete ${className} with id of ${id4Deletion} due to incomplete decoupling.`)
7
+ }
8
+
9
+
10
+ export function validateDependentDataDecoupling(dependentsData, id4deletion) {
11
+ for (const dependency2dArr of Object.values(dependentsData)) {
12
+ const [dependentInstanceArr, props2Check] = dependency2dArr
13
+ for (const instance of dependentInstanceArr) {
14
+ for (const prop of props2Check) {
15
+ if (instance[prop].id === id4deletion) return false
16
+ }
17
+ }
18
+ }
19
+ return true
20
+ }
@@ -0,0 +1,46 @@
1
+ import { OrmStore } from "../../misc/ormStore.js"
2
+ import { DependentsFinalizationRegistry, ORM } from "../../ORM/ORM.js"
3
+ import { aliasedFindWiki2QueryRes, parseFindWiki } from "../find/find.js"
4
+ import { deproxifyScopeProxy, classWiki2ScopeProxy } from "../find/scopeProxies.js"
5
+ import { postgresCreateProxyArray } from "../find/sqlClients/postgresFuncs.js"
6
+ import { sqliteCreateProxyArray } from "../find/sqlClients/sqliteFuncs.js"
7
+
8
+ export async function internalFind(dependentMap, relationalProps, searchedId) {
9
+ const { sqlClient, dbConnection, entities } = OrmStore.store
10
+ const baseProxyMap = classWiki2ScopeProxy({ ...dependentMap })
11
+ let findWiki = deproxifyScopeProxy(baseProxyMap)
12
+ const eagerLoadObj = {}
13
+ for (const prop of relationalProps) internalFindSetup(prop, findWiki, eagerLoadObj, searchedId)
14
+
15
+ const [aliasedFindMap, joinStatements, whereObj] = parseFindWiki(findWiki)
16
+ const [queryResult, eagerLoadMap] = await aliasedFindWiki2QueryRes(aliasedFindMap, joinStatements, whereObj, eagerLoadObj, dependentMap, dbConnection, true)
17
+ const instanceArr = sqlClient === "postgresql" ?
18
+ postgresCreateProxyArray(queryResult, eagerLoadMap, entities, eagerLoadObj) :
19
+ sqliteCreateProxyArray(queryResult, eagerLoadMap, entities, true)
20
+
21
+ return instanceArr
22
+ }
23
+
24
+
25
+ export function insertDependentsData(className, dependedOnId, dependentsData, dependentsMapsObj) {
26
+ const map = dependentsMapsObj[className] ??= new Map()
27
+ map.set(dependedOnId, new WeakRef(dependentsData))
28
+ ORM[DependentsFinalizationRegistry].register(dependentsData, [className, dependedOnId])
29
+ }
30
+
31
+
32
+
33
+ export function internalFindSetup(prop, findWiki, eagerLoadObj, searchedId) {
34
+ const { [prop]: relation, ...restOfRelations } = findWiki.uncalledJunctions_
35
+ const relationCopy = { ...relation }
36
+ relationCopy.where = { id: searchedId }
37
+ for (const key of Object.keys(relationCopy)) {
38
+ relationCopy[key + `_`] = relationCopy[key]
39
+ delete relationCopy[key]
40
+ }
41
+ const { junctions_, ...noJunctionsRelation } = relationCopy
42
+ findWiki.uncalledJunctions_ = restOfRelations
43
+ findWiki.junctions_ ??= {}
44
+ findWiki.junctions_[prop] = noJunctionsRelation
45
+ eagerLoadObj[prop] = true
46
+ }
@@ -0,0 +1,46 @@
1
+
2
+ import type { FindObj } from "../misc/types"
3
+
4
+ export declare class Entity {
5
+ id: string | number | bigint
6
+ updatedAt: Date
7
+
8
+ /**
9
+ * Finds instances in the database that match the given argument.
10
+ * Relations do not get filtered by where conditions, only the root instances get filtered.
11
+ * (RootClass.find(arg)) => only RootClass instances matching the where conditions get returned.
12
+ */
13
+ static find<T extends Entity>(
14
+ this: new (...args: any[]) => T,
15
+ obj: FindObj<T>
16
+ ): Promise<T[]>
17
+
18
+ /**
19
+ * Hard deletes the instance from the database. May require a pre-deletion step - the 'getDependents' method.
20
+ */
21
+ delete(): Promise<void>
22
+
23
+ /**
24
+ * A pre-deletion step that is required in certain cases.
25
+ * Finds all instances that have a one-to-one relationship with the calling instance
26
+ * where the related property cannot be set to `undefined`.
27
+ * These relationships must be reassigned before the calling instance
28
+ * can be safely deleted.
29
+ * Returns undefined if there are no dependents.
30
+ */
31
+ getDependents(): Promise<DependentsDict | undefined>
32
+
33
+ /**
34
+ * Finds all instances that have a relation with the calling instance,
35
+ * This method is a superset of the getDependents method, and is not meant as a pre-deletion step, but as a utility.
36
+ */
37
+ getReferencers(): Promise<DependentsDict | undefined>
38
+ }
39
+
40
+
41
+ type DependentsDict = {
42
+ [key: string]: [
43
+ dependentInstances: any[],
44
+ dependentProps: string[]
45
+ ]
46
+ }