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.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/bin/universalTsInit.js +59 -0
- package/docs/deletion.md +186 -0
- package/docs/find.md +265 -0
- package/docs/getting-started-javascript.md +112 -0
- package/docs/getting-started-typescript.md +159 -0
- package/docs/in-depth-class-definitions.md +193 -0
- package/docs/jsdoc-ux-tips.md +17 -0
- package/docs/managing-the-database.md +37 -0
- package/docs/saving-to-database.md +37 -0
- package/index.d.ts +7 -0
- package/index.js +11 -0
- package/jsconfig.json +29 -0
- package/package.json +40 -0
- package/src/ORM/DbManager.js +76 -0
- package/src/ORM/ORM.js +181 -0
- package/src/ORM/bootOrm.js +929 -0
- package/src/changeLogger/changeLogger.js +55 -0
- package/src/changeLogger/save.js +237 -0
- package/src/changeLogger/sqlClients/postgres.js +97 -0
- package/src/changeLogger/sqlClients/sqlite.js +120 -0
- package/src/entity/delete/delete.js +20 -0
- package/src/entity/delete/getDependents.js +46 -0
- package/src/entity/entity.d.ts +46 -0
- package/src/entity/entity.js +228 -0
- package/src/entity/find/find.js +157 -0
- package/src/entity/find/joins.js +52 -0
- package/src/entity/find/queryBuilder.js +95 -0
- package/src/entity/find/relations.js +23 -0
- package/src/entity/find/scopeProxies.js +60 -0
- package/src/entity/find/sqlClients/postgresFuncs.js +171 -0
- package/src/entity/find/sqlClients/sqliteFuncs.js +211 -0
- package/src/entity/find/where/relationalWhere.js +142 -0
- package/src/entity/find/where/where.js +244 -0
- package/src/entity/find/where/whereArgsFunctions.js +49 -0
- package/src/misc/classes.js +63 -0
- package/src/misc/constants.js +42 -0
- package/src/misc/miscFunctions.js +167 -0
- package/src/misc/ormStore.js +18 -0
- package/src/misc/types.d.ts +560 -0
- package/src/proxies/instanceProxy.js +544 -0
- package/src/proxies/nonRelationalArrayProxy.js +76 -0
- package/src/proxies/objectProxy.js +50 -0
- package/src/proxies/relationalArrayProxy.js +130 -0
- package/src/webpack/masquerade-loader.js +14 -0
- package/src/webpack/plugin.js +43 -0
- package/src/webpack/store.js +2 -0
- package/src/webpack/webpack.config.js +41 -0
- package/testing/dev-doc.md +7 -0
- package/testing/generationFuncs.js +21 -0
- package/testing/miscFunctions.js +97 -0
- package/testing/postgres.test.js +254 -0
- package/testing/sqlite.test.js +257 -0
- package/testing/testing-classes.js +102 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
|
|
2
|
+
import { setMaxListeners } from 'events'
|
|
3
|
+
import { newEntityInstanceSymb } from '../misc/constants.js'
|
|
4
|
+
import { fillCalledRelationsOnInstance, junctionProp2Wiki, getRelationalPropNames, traverseResultObjRelationalScope } from '../entity/find/find.js'
|
|
5
|
+
import { postgresDbValHandling } from '../entity/find/sqlClients/postgresFuncs.js'
|
|
6
|
+
import { sqliteDbValHandling } from '../entity/find/sqlClients/sqliteFuncs.js'
|
|
7
|
+
import { LazyPromise } from '../misc/classes.js'
|
|
8
|
+
import { FinalizationRegistrySymb, ORM } from '../ORM/ORM.js'
|
|
9
|
+
import { coloredBackgroundConsoleLog, getPropertyClassification, js2SqlTyping, nonSnake2Snake, postgres2sqliteQueryStr, snake2Pascal } from '../misc/miscFunctions.js'
|
|
10
|
+
import { createNonRelationalArrayProxy } from './nonRelationalArrayProxy.js'
|
|
11
|
+
import { createObjectProxy } from './objectProxy.js'
|
|
12
|
+
import { createRelationalArrayProxy } from './relationalArrayProxy.js'
|
|
13
|
+
import { ChangeLogger } from '../changeLogger/changeLogger.js'
|
|
14
|
+
import { OrmStore } from '../misc/ormStore.js'
|
|
15
|
+
setMaxListeners(577)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export function rowObj2InstanceProxy(resultObj, findWiki, Entities) {
|
|
19
|
+
const className = findWiki.className
|
|
20
|
+
const { nonRelationalProperties, relationalProperties, uncalledRelationalProperties } = getCategorizedClassProperties(findWiki)
|
|
21
|
+
|
|
22
|
+
const { entityMapsObj, sqlClient } = OrmStore.store
|
|
23
|
+
if (!entityMapsObj[className]) entityMapsObj[className] = new Map()
|
|
24
|
+
let entityMap = entityMapsObj[className]
|
|
25
|
+
const instanceOnLogger = searchEntityMap(resultObj, relationalProperties, entityMap)
|
|
26
|
+
|
|
27
|
+
if (instanceOnLogger) {
|
|
28
|
+
const [proxy, relationalProperties2Add] = instanceOnLogger
|
|
29
|
+
const deproxied = proxy.source_
|
|
30
|
+
let existingRelations2Traverse
|
|
31
|
+
if (relationalProperties2Add.length) {
|
|
32
|
+
//adding relational data to the source of the proxy that is on the entityMap
|
|
33
|
+
fillCalledRelationsOnInstance(deproxied, resultObj, findWiki, relationalProperties2Add, Entities)
|
|
34
|
+
existingRelations2Traverse = relationalProperties.filter(relation => !relationalProperties2Add.includes(relation))
|
|
35
|
+
}
|
|
36
|
+
else existingRelations2Traverse = relationalProperties
|
|
37
|
+
//also need to account for relations that are already loaded and dont need to be added, traversing their scope and potentially expanding it.
|
|
38
|
+
for (const relation2Traverse of existingRelations2Traverse) {
|
|
39
|
+
const mapWithRelation = junctionProp2Wiki(findWiki, relation2Traverse)
|
|
40
|
+
traverseResultObjRelationalScope(resultObj[relation2Traverse], mapWithRelation, Entities)
|
|
41
|
+
}
|
|
42
|
+
return proxy
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
//data from db
|
|
46
|
+
const instanceClass = Entities[className]
|
|
47
|
+
const instance = Object.create(instanceClass.prototype)
|
|
48
|
+
|
|
49
|
+
for (const propertyName of nonRelationalProperties) {
|
|
50
|
+
let value = resultObj[propertyName]
|
|
51
|
+
if (value === null) {
|
|
52
|
+
instance[propertyName] = undefined
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (sqlClient === `postgresql`) postgresDbValHandling(instance, propertyName, value, findWiki)
|
|
57
|
+
else sqliteDbValHandling(instance, propertyName, value, findWiki)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (relationalProperties.length) fillCalledRelationsOnInstance(instance, resultObj, findWiki, relationalProperties, Entities)
|
|
61
|
+
|
|
62
|
+
if (uncalledRelationalProperties.length) {
|
|
63
|
+
for (const property of uncalledRelationalProperties) {
|
|
64
|
+
let currentWiki = findWiki
|
|
65
|
+
while (!currentWiki.uncalledJunctions[property]) currentWiki = currentWiki.parent
|
|
66
|
+
const uncalledJunctionObj = currentWiki.uncalledJunctions[property]
|
|
67
|
+
const nameOfMapWithJunction = uncalledJunctionObj.className
|
|
68
|
+
const promiseOf = uncalledJunctionObj.isArray ? nameOfMapWithJunction + `[]` : nameOfMapWithJunction
|
|
69
|
+
instance[property] = new LazyPromise(promiseOf)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const proxy = proxifyEntityInstanceObj(instance, uncalledRelationalProperties)
|
|
74
|
+
insertProxyIntoEntityMap(proxy, entityMap)
|
|
75
|
+
return proxy
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function insertProxyIntoEntityMap(proxy, entityMap) {
|
|
80
|
+
entityMap.set(proxy.id, new WeakRef(proxy))
|
|
81
|
+
ORM[FinalizationRegistrySymb].register(proxy, [proxy.constructor.name, proxy.id])
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function instanceProxyGetHandler(target, key, classWiki) {
|
|
85
|
+
const val = target[key]
|
|
86
|
+
if (!(val instanceof LazyPromise)) return val
|
|
87
|
+
|
|
88
|
+
const { sqlClient, dbConnection } = OrmStore.store
|
|
89
|
+
const [classification, joinedClassMap, mapWithProp] = getPropertyClassification(key, classWiki)
|
|
90
|
+
let joinedTable
|
|
91
|
+
|
|
92
|
+
if (classification === "Join") joinedTable = classWiki.junctions[key]
|
|
93
|
+
else joinedTable = mapWithProp.junctions[key]
|
|
94
|
+
|
|
95
|
+
const isArrayOfInstances = joinedClassMap.isArray
|
|
96
|
+
|
|
97
|
+
let queryStr = `SELECT entity.* FROM ${nonSnake2Snake(mapWithProp.className)}___${nonSnake2Snake(key)}_jt jt` +
|
|
98
|
+
` LEFT JOIN ${nonSnake2Snake(joinedClassMap.className)} entity ON jt.joined_id = entity.id WHERE jt.joining_id = `
|
|
99
|
+
queryStr += sqlClient === "postgresql" ? `$1` : `?`
|
|
100
|
+
|
|
101
|
+
let queryFunc
|
|
102
|
+
if (sqlClient === "postgresql") queryFunc = (queryStr, id) => dbConnection.query(queryStr, [id])
|
|
103
|
+
else queryFunc = (queryStr, id) => dbConnection.prepare(queryStr).all(id)
|
|
104
|
+
|
|
105
|
+
let promise
|
|
106
|
+
try {
|
|
107
|
+
let queryRes = queryFunc(queryStr, target.id)
|
|
108
|
+
promise = createLazyPromise(target, key, queryRes, mapWithProp.junctions[key], isArrayOfInstances, sqlClient)
|
|
109
|
+
promise.then(res => target[key] = res)
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
coloredBackgroundConsoleLog(`Lazy loading failed. ${e}\n`, `failure`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return promise
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function instanceProxySetHandler(target, key, value, eventListenersObj, classWiki) {
|
|
119
|
+
const { dbChangesObj } = OrmStore.store
|
|
120
|
+
let oldValue = target[key]
|
|
121
|
+
if (oldValue === value) return
|
|
122
|
+
if (value === null) value = undefined
|
|
123
|
+
const entityClass = target.constructor.name
|
|
124
|
+
let valChanged = true
|
|
125
|
+
|
|
126
|
+
const [classification, columnType, classContainingProperty] = getPropertyClassification(key, classWiki)
|
|
127
|
+
const isArray = columnType.isArray
|
|
128
|
+
|
|
129
|
+
if (oldValue instanceof LazyPromise) uncalledPropertySetHandler(target, key, value, [classification, columnType, classContainingProperty])
|
|
130
|
+
// if (oldValue instanceof LazyPromise) coloredBackgroundConsoleLog(`Do not overwrite a LazyPromise value. Please use the static 'setUnloadedVal' method of LazyPromise instead. Note: this will wipe any unloaded relational data.`, `warning`)
|
|
131
|
+
else if (key === "id") coloredBackgroundConsoleLog(`Warning: do not assign id values. Id value is unchanged.`, `warning`)
|
|
132
|
+
else if (classification === "Primitive" || classification === "ParentPrimitive") {
|
|
133
|
+
const expectedValType = columnType.type
|
|
134
|
+
const valType = Array.isArray(value) ? "array" : value instanceof Date ? "Date" : typeof value
|
|
135
|
+
//date is considered an object and shouldnt be proxified
|
|
136
|
+
|
|
137
|
+
if (valType === "array") {
|
|
138
|
+
if (isArray && expectedValType === `object`) target[key] = createNonRelationalArrayProxy(target, key, value, expectedValType, true)
|
|
139
|
+
else if (isArray) target[key] = createNonRelationalArrayProxy(target, key, value, expectedValType) //includes only valid data
|
|
140
|
+
else {
|
|
141
|
+
valChanged = false
|
|
142
|
+
if (oldValue !== undefined) coloredBackgroundConsoleLog(`Warning: do not assign arrays to property '${key}' of class ${entityClass} which is of type ${expectedValType}. Value remains unchanged.`, `warning`)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else if (valType === "object") {
|
|
146
|
+
if (expectedValType === 'object') target[key] = createObjectProxy(target, key, value)
|
|
147
|
+
else {
|
|
148
|
+
valChanged = false
|
|
149
|
+
if (oldValue !== undefined) coloredBackgroundConsoleLog(`Warning: do not assign objects to property '${key}' of class ${entityClass} which is of type ${expectedValType}. Value remains unchanged.`, `warning`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else if (valType !== expectedValType) {
|
|
153
|
+
if (!value && columnType.optional) target[key] = value
|
|
154
|
+
else {
|
|
155
|
+
valChanged = false
|
|
156
|
+
if (oldValue !== undefined) coloredBackgroundConsoleLog(`Warning: do not assign values of type '${valType}' to property '${key}' of class ${entityClass}. This property expects a value of type '${expectedValType}'. Value remains unchanged.`, `warning`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else target[key] = value
|
|
160
|
+
|
|
161
|
+
if (valChanged) insertIntoDbChanges(target, key, value, dbChangesObj, entityClass, isArray, expectedValType)
|
|
162
|
+
}
|
|
163
|
+
else if (classification === "Join" || classification === "ParentJoin") {
|
|
164
|
+
const expectedValType = columnType.className
|
|
165
|
+
const isOptional = columnType.optional
|
|
166
|
+
dbChangesObj[entityClass] ??= {}
|
|
167
|
+
const instanceChangeObj = dbChangesObj[entityClass][target.id] ??= {}
|
|
168
|
+
|
|
169
|
+
if (isArray) {
|
|
170
|
+
const isVald = validateRelationalValSetting(target, key, value, isArray, expectedValType, isOptional, oldValue, entityClass)
|
|
171
|
+
if (!isVald) return
|
|
172
|
+
|
|
173
|
+
const newArray = createRelationalArrayProxy(target, key, value, expectedValType) //includes only valid data
|
|
174
|
+
target[key] = newArray
|
|
175
|
+
|
|
176
|
+
const oldIds = oldValue === undefined ? [] : oldValue.length ? oldValue.map(entity => entity.id) : []
|
|
177
|
+
const newIds = newArray.length ? newArray.map(entity => entity.id) : []
|
|
178
|
+
|
|
179
|
+
const added = newIds.length ? newIds.filter(id => !oldIds.includes(id)) : []
|
|
180
|
+
const removed = oldIds.length ? oldIds.filter(id => !newIds.includes(id)) : []
|
|
181
|
+
if (!added.length && !removed.length) return
|
|
182
|
+
|
|
183
|
+
const changeLogger = instanceChangeObj[key] ??= { added: [], removed: [] }
|
|
184
|
+
changeLogger.added.push(...added)
|
|
185
|
+
changeLogger.removed.push(...removed)
|
|
186
|
+
|
|
187
|
+
const prefilteredAdded = [...changeLogger.added]
|
|
188
|
+
const prefilteredRemoved = [...changeLogger.removed]
|
|
189
|
+
|
|
190
|
+
changeLogger.added = prefilteredAdded.filter(id => !prefilteredRemoved.includes(id))
|
|
191
|
+
changeLogger.removed = prefilteredRemoved.filter(id => !prefilteredAdded.includes(id))
|
|
192
|
+
}
|
|
193
|
+
else if (!isArray) {
|
|
194
|
+
const isValid = validateRelationalValSetting(target, key, value, isArray, expectedValType, isOptional, oldValue, entityClass)
|
|
195
|
+
if (!isValid) return
|
|
196
|
+
|
|
197
|
+
target[key] = value
|
|
198
|
+
const changeLogger = instanceChangeObj[key] ??= { added: [], removed: [] }
|
|
199
|
+
if (value) {
|
|
200
|
+
addEventListener2Proxy(target, key, eventListenersObj, oldValue)
|
|
201
|
+
const index = changeLogger.removed.indexOf(value.id)
|
|
202
|
+
if (index !== -1) changeLogger.removed.splice(index, 1)
|
|
203
|
+
else changeLogger.added.push(value.id)
|
|
204
|
+
}
|
|
205
|
+
if (oldValue) {
|
|
206
|
+
const index = changeLogger.added.indexOf(oldValue.id)
|
|
207
|
+
if (index !== -1) changeLogger.added.splice(index, 1)
|
|
208
|
+
else changeLogger.removed.push(oldValue.id)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
setUpdatedAtValue(target, instanceChangeObj)
|
|
212
|
+
}
|
|
213
|
+
else target[key] = value
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function setUpdatedAtValue(targetInstance, instanceChangeObj) {
|
|
217
|
+
const now = new Date()
|
|
218
|
+
targetInstance.updatedAt = now
|
|
219
|
+
instanceChangeObj.updatedAt = now
|
|
220
|
+
ChangeLogger.flushChanges()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function validateRelationalValSetting(target, key, value, isArray, expectedValType, isOptional, oldValue, entityClass) {
|
|
224
|
+
if (isArray) {
|
|
225
|
+
if (!Array.isArray(value)) {
|
|
226
|
+
if (!value && isOptional) target[key] = value
|
|
227
|
+
else {
|
|
228
|
+
if (oldValue !== undefined) {
|
|
229
|
+
let warningStr = `Warning: improper value assigment to property '${key}' of class ${entityClass}. Expected value of type ${expectedValType}[]`
|
|
230
|
+
warningStr += isOptional ? ` | undefined.` : `.`
|
|
231
|
+
coloredBackgroundConsoleLog(warningStr + ` Value remains unchanged.`, `warning`)
|
|
232
|
+
}
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
if (!value) {
|
|
239
|
+
if (isOptional) target[key] = value
|
|
240
|
+
else {
|
|
241
|
+
if (oldValue !== undefined) coloredBackgroundConsoleLog(`Warning: do not assign values of type 'undefined' to property '${key}' of class ${entityClass}. This property expects a value of type '${expectedValType}'. Value remains unchanged.`, `warning`)
|
|
242
|
+
return false
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (!value.id || value.constructor.name !== expectedValType) {
|
|
246
|
+
let warningStr = `Warning: improper value assigment to property '${key}' of class ${entityClass}. Expected value of type ${expectedValType}`
|
|
247
|
+
warningStr += isOptional ? ` | undefind.` : `.`
|
|
248
|
+
if (oldValue !== undefined) coloredBackgroundConsoleLog(warningStr + ` Value remains unchanged.`, `warning`)
|
|
249
|
+
return false
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return true
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function insertIntoDbChanges(target, key, value, changesObj, entityClass, isArray, columnType) {
|
|
256
|
+
changesObj[entityClass] ??= {}
|
|
257
|
+
const entityChangeObj = changesObj[entityClass][target.id] ??= {}
|
|
258
|
+
if (isArray && columnType === `object`) entityChangeObj[key] = JSON.stringify(value)
|
|
259
|
+
else entityChangeObj[key] = value
|
|
260
|
+
setUpdatedAtValue(target, entityChangeObj)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function addEventListener2Proxy(listeningInstance, key, eventListenersObj, oldListened2Proxy) {
|
|
264
|
+
const newListened2Proxy = listeningInstance[key]
|
|
265
|
+
if (!newListened2Proxy) return
|
|
266
|
+
const oldEventFunc = eventListenersObj[key]
|
|
267
|
+
const emitter = newListened2Proxy.eEmitter_
|
|
268
|
+
if (oldEventFunc) oldListened2Proxy.eEmitter_.removeEventListener("delete", oldEventFunc)
|
|
269
|
+
if (!newListened2Proxy || !newListened2Proxy.id) return
|
|
270
|
+
|
|
271
|
+
const eventFunc = (event) => {
|
|
272
|
+
if (listeningInstance._isDeleted_) return
|
|
273
|
+
// console.log(`Delete event received by ${listeningInstance.id}_${listeningInstance.constructor.name} on property '${key}' from ${newListened2Proxy.id}_${newListened2Proxy.constructor.name}`)
|
|
274
|
+
const id2delete = event.detail.id
|
|
275
|
+
if (listeningInstance[key].id === id2delete) {
|
|
276
|
+
listeningInstance[key] = undefined
|
|
277
|
+
delete eventListenersObj[key]
|
|
278
|
+
logDeletionEvent(listeningInstance, key, id2delete)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
eventListenersObj[key] = eventFunc
|
|
282
|
+
|
|
283
|
+
emitter.addEventListener(
|
|
284
|
+
"delete",
|
|
285
|
+
eventFunc,
|
|
286
|
+
{ once: true }
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
function logDeletionEvent(listeningInstance, key, id2delete) {
|
|
292
|
+
const instanceChangeObj = OrmStore.getClassChangesObj(listeningInstance.constructor.name)
|
|
293
|
+
const instanceChangesObj = instanceChangeObj[listeningInstance.id] ??= {}
|
|
294
|
+
if (!instanceChangesObj[key]) {
|
|
295
|
+
instanceChangesObj[key] = { added: [], removed: [id2delete] }
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
if (instanceChangesObj[key].added.includes(id2delete)) instanceChangesObj[key].added = []
|
|
299
|
+
else instanceChangesObj[key].removed = [id2delete]
|
|
300
|
+
}
|
|
301
|
+
setUpdatedAtValue(listeningInstance, instanceChangesObj)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function findOrInsertInInstanceLogger(instance, classLoggingMap, nonRelationalPropertiesObj, instanceRelations) {
|
|
305
|
+
let instanceOnLogger = classLoggingMap.get(instance.id)
|
|
306
|
+
const nonRelationalProperties = Object.keys(nonRelationalPropertiesObj)
|
|
307
|
+
const instanceProperties = Object.keys(instance)
|
|
308
|
+
|
|
309
|
+
for (const prop of instanceProperties) {
|
|
310
|
+
if (nonRelationalProperties.includes(prop)) continue
|
|
311
|
+
instanceRelations[prop] = instance[prop]
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (instanceOnLogger) {
|
|
315
|
+
instanceOnLogger = instanceOnLogger.deref()
|
|
316
|
+
for (const prop of nonRelationalProperties) instanceRelations[prop] = instanceOnLogger[prop]
|
|
317
|
+
return instanceOnLogger
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let relationlessInstance = structuredClone(instanceRelations)
|
|
321
|
+
for (const prop of nonRelationalProperties) {
|
|
322
|
+
relationlessInstance[prop] = instance[prop]
|
|
323
|
+
instanceRelations[prop] = instance[prop]
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
insertIntoEntityMap(relationlessInstance, classLoggingMap)
|
|
327
|
+
return relationlessInstance
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function createLazyLoadQueryStr(property, classWiki) {
|
|
331
|
+
const joinedTable = classWiki.junctions[property]
|
|
332
|
+
const baseTableName = nonSnake2Snake(classWiki.className)
|
|
333
|
+
|
|
334
|
+
const baseJtName = `${baseTableName}___${nonSnake2Snake(property)}_jt`
|
|
335
|
+
const promisedEntityTableName = nonSnake2Snake(joinedTable.className)
|
|
336
|
+
|
|
337
|
+
let selectStr = `SELECT entity.*`
|
|
338
|
+
let queryStr = ` FROM ${baseJtName} jt \n`
|
|
339
|
+
queryStr += `LEFT JOIN ${promisedEntityTableName} entity ON jt.${promisedEntityTableName}_id = entity.id \n`
|
|
340
|
+
|
|
341
|
+
if (joinedTable.parent) {
|
|
342
|
+
let currentTable = joinedTable
|
|
343
|
+
let i = 1
|
|
344
|
+
while (currentTable.parent) {
|
|
345
|
+
selectStr += `, entity${i}.*`
|
|
346
|
+
queryStr += ` LEFT JOIN ${nonSnake2Snake(currentTable.parent.className)} entity${i} ON entity${i === 1 ? '' : i - 1}.id = entity${i}.id \n`
|
|
347
|
+
currentTable = currentTable.parent
|
|
348
|
+
i++
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
queryStr += `WHERE jt.${baseTableName}_id = $1;`
|
|
352
|
+
queryStr = selectStr + queryStr
|
|
353
|
+
|
|
354
|
+
if (OrmStore.store.sqlClient === "sqlite") queryStr = postgres2sqliteQueryStr(queryStr)
|
|
355
|
+
return queryStr
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
export function createLazyPromise(target, key, queryRes, classWiki, isArrayOfInstances, client) {
|
|
360
|
+
return new Promise(async (resolve) => {
|
|
361
|
+
let resultArr
|
|
362
|
+
try {
|
|
363
|
+
if (client === "postgresql") resultArr = (await queryRes).rows
|
|
364
|
+
else resultArr = queryRes
|
|
365
|
+
|
|
366
|
+
if (!resultArr.length) {
|
|
367
|
+
if (isArrayOfInstances) {
|
|
368
|
+
target[key] = []
|
|
369
|
+
resolve([])
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
target[key] = undefined
|
|
373
|
+
resolve(undefined)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const { className, columns, junctions } = classWiki
|
|
378
|
+
const findWiki = { className, columns, uncalledJunctions: junctions }
|
|
379
|
+
let currentWiki = classWiki
|
|
380
|
+
let currentScopedMap = findWiki
|
|
381
|
+
while (currentWiki.parent) {
|
|
382
|
+
const { className, columns, junctions } = currentWiki.parent
|
|
383
|
+
currentScopedMap.parent = { className, columns, uncalledJunctions: junctions }
|
|
384
|
+
currentScopedMap = currentScopedMap.parent
|
|
385
|
+
currentWiki = currentWiki.parent
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const proxyArr = []
|
|
389
|
+
for (const row of resultArr) {
|
|
390
|
+
const rowWithCamelCasedProps = Object.fromEntries(Object.entries(row).map(([key, val]) => [snake2Pascal(key, true), val]))
|
|
391
|
+
proxyArr.push(rowObj2InstanceProxy(rowWithCamelCasedProps, findWiki, OrmStore.store.entities))
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (isArrayOfInstances) {
|
|
395
|
+
target[key] = createRelationalArrayProxy(target, key, proxyArr, classWiki.className)
|
|
396
|
+
resolve(target[key])
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
target[key] = proxyArr[0]
|
|
400
|
+
resolve(target[key])
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
catch (e) {
|
|
404
|
+
coloredBackgroundConsoleLog(`Lazy loading failed. ${e}\n`, `failure`)
|
|
405
|
+
}
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
export function uncalledPropertySetHandler(target, key, value, columnClassificationArr) {
|
|
411
|
+
const [propertyType, propertyTypeObj, mapWithProp] = columnClassificationArr
|
|
412
|
+
const joiningId = target.id
|
|
413
|
+
const nameOfClassWithProp = mapWithProp.className
|
|
414
|
+
// if (propertyType === "Join" || propertyType === "ParentJoin") {
|
|
415
|
+
// }
|
|
416
|
+
//todo check if is optional against a potentially undefined value
|
|
417
|
+
|
|
418
|
+
const { dbChangesObj, sqlClient } = OrmStore.store
|
|
419
|
+
const classChangeObj = dbChangesObj[target.constructor.name] ??= {}
|
|
420
|
+
const instanceChangeObj = classChangeObj[target.id] ??= {}
|
|
421
|
+
const { isArray, optional } = propertyTypeObj
|
|
422
|
+
|
|
423
|
+
if (!value && optional) target[key] = value
|
|
424
|
+
else if (isArray && Array.isArray(value)) {
|
|
425
|
+
target[key] = createRelationalArrayProxy(target, key, [], propertyTypeObj.className)
|
|
426
|
+
for (const instance of value) target[key].push(instance)
|
|
427
|
+
setUpdatedAtValue(target, instanceChangeObj)
|
|
428
|
+
}
|
|
429
|
+
else if (!isArray && !Array.isArray(value)) {
|
|
430
|
+
target[key] = value
|
|
431
|
+
instanceChangeObj[key] = { added: [value.id], removed: [] }
|
|
432
|
+
setUpdatedAtValue(target, instanceChangeObj)
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
coloredBackgroundConsoleLog(`Warning: Incorrect value type assigment attempt to property '${key}' of class ${nameOfClassWithProp}. Promise value remains unchanged.`, `warning`)
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const junctionTableName = `${nonSnake2Snake(nameOfClassWithProp)}___${nonSnake2Snake(key)}_jt`
|
|
440
|
+
const idType = js2SqlTyping(sqlClient, mapWithProp.columns.id.type)
|
|
441
|
+
|
|
442
|
+
const uncalledPropChangeObj = dbChangesObj.deletedUncalledRelationsArr ??= {}
|
|
443
|
+
const junctionChangeArr = uncalledPropChangeObj[junctionTableName] ??= { idType, params: [] }
|
|
444
|
+
junctionChangeArr.params.push(joiningId)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
export function getCategorizedClassProperties(classWiki) {
|
|
449
|
+
let nonRelationalProperties = [...Object.keys(classWiki.columns ?? {})]
|
|
450
|
+
const relationalProperties = [...Object.keys(classWiki.junctions ?? {})]
|
|
451
|
+
const uncalledRelationalProperties = [...Object.keys(classWiki.uncalledJunctions ?? {})]
|
|
452
|
+
if (classWiki.parent) {
|
|
453
|
+
let currentClass = classWiki
|
|
454
|
+
while (currentClass.parent) {
|
|
455
|
+
nonRelationalProperties.push(...Object.keys(currentClass.parent.columns ?? {}))
|
|
456
|
+
relationalProperties.push(...Object.keys(currentClass.parent.junctions ?? {}))
|
|
457
|
+
uncalledRelationalProperties.push(...Object.keys(currentClass.parent.uncalledJunctions ?? {}))
|
|
458
|
+
currentClass = currentClass.parent
|
|
459
|
+
}
|
|
460
|
+
nonRelationalProperties = nonRelationalProperties.filter(property => property !== 'id')
|
|
461
|
+
nonRelationalProperties.unshift('id')
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return { nonRelationalProperties, relationalProperties, uncalledRelationalProperties }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
export function proxifyEntityInstanceObj(instance, uncalledRelationalProperties) {
|
|
469
|
+
// TODO POTENTIALLY CHECK IF IT IS A PROXY AND IF SO RETURN
|
|
470
|
+
// TODO is there a point in even checking if new values are proxies????
|
|
471
|
+
|
|
472
|
+
if (instance === undefined || !instance.id) return instance
|
|
473
|
+
const instanceClassName = instance.constructor.name
|
|
474
|
+
const { classWikiDict, dbChangesObj } = OrmStore.store
|
|
475
|
+
const classWiki = classWikiDict[instanceClassName]
|
|
476
|
+
|
|
477
|
+
if (!uncalledRelationalProperties) {
|
|
478
|
+
//new instance
|
|
479
|
+
dbChangesObj[instanceClassName] ??= {}
|
|
480
|
+
const newEntityChangeObj = dbChangesObj[instanceClassName][instance.id] ??= {}
|
|
481
|
+
|
|
482
|
+
// const idVal = instance.id
|
|
483
|
+
//newEntityChangeObj.id = typeof idVal === `bigint` ? idVal.toString() : idVal
|
|
484
|
+
newEntityChangeObj.id = instance.id
|
|
485
|
+
newEntityChangeObj.updatedAt = instance.updatedAt
|
|
486
|
+
newEntityChangeObj[newEntityInstanceSymb] = true
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
const emitter = new EventTarget()
|
|
491
|
+
const eventListenersObj = {}
|
|
492
|
+
const handler = {
|
|
493
|
+
get(target, key, receiver) {
|
|
494
|
+
if (key === "constructor") return instance.constructor
|
|
495
|
+
else if (key === "source_") return target
|
|
496
|
+
else if (key === "eEmitter_") return emitter
|
|
497
|
+
else if (key === "eListener_") return eventListenersObj
|
|
498
|
+
else if (key === "_isDeleted_") return target[key]
|
|
499
|
+
return instanceProxyGetHandler(target, key, classWiki)
|
|
500
|
+
},
|
|
501
|
+
set: (target, /**@type {string}*/ key, value) => {
|
|
502
|
+
instanceProxySetHandler(target, key, value, eventListenersObj, classWiki)
|
|
503
|
+
return true
|
|
504
|
+
},
|
|
505
|
+
defineProperty: (target, /**@type {string}*/ key, definePropObj) => {
|
|
506
|
+
instanceProxySetHandler(target, key, definePropObj.value, eventListenersObj, classWiki)
|
|
507
|
+
return true
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const proxy = new Proxy(instance, handler)
|
|
511
|
+
if (uncalledRelationalProperties) { //found instance
|
|
512
|
+
const relational1To1Props = getRelationalPropNames(classWiki, true)
|
|
513
|
+
const relational1To1sExcludingPromises = relational1To1Props.filter(propName => !uncalledRelationalProperties.includes(propName))
|
|
514
|
+
for (const prop of relational1To1sExcludingPromises) addEventListener2Proxy(proxy.source_, prop, eventListenersObj)
|
|
515
|
+
}
|
|
516
|
+
return proxy
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
export function insertIntoEntityMap(instance, entityMap) {
|
|
521
|
+
entityMap.set(instance.id, new WeakRef(instance))
|
|
522
|
+
ORM[FinalizationRegistrySymb].register(instance, [instance.constructor.name, instance.id])
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
export function searchEntityMap(resultObj, calledRelationalPropNamesArr, entityMap) {
|
|
527
|
+
//searches for the instance on the map and returns either false or returns an array containing the instance + the relational prop names to add as an arr
|
|
528
|
+
const instanceId = resultObj.id
|
|
529
|
+
let proxyOnLogger = entityMap.get(instanceId)
|
|
530
|
+
if (proxyOnLogger) {
|
|
531
|
+
proxyOnLogger = proxyOnLogger.deref()
|
|
532
|
+
if (!calledRelationalPropNamesArr.length) return [proxyOnLogger, []]
|
|
533
|
+
|
|
534
|
+
let unproxied = proxyOnLogger.source_
|
|
535
|
+
const relationalProperties2Add = []
|
|
536
|
+
|
|
537
|
+
for (const relationalProp of calledRelationalPropNamesArr) {
|
|
538
|
+
const onLoggerVal = unproxied[relationalProp]
|
|
539
|
+
if (onLoggerVal instanceof LazyPromise) relationalProperties2Add.push(relationalProp)
|
|
540
|
+
}
|
|
541
|
+
return [proxyOnLogger, relationalProperties2Add]
|
|
542
|
+
}
|
|
543
|
+
else return false
|
|
544
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ChangeLogger } from "../changeLogger/changeLogger.js"
|
|
2
|
+
import { getValidTypedArray } from "../misc/miscFunctions.js"
|
|
3
|
+
import { OrmStore } from "../misc/ormStore.js"
|
|
4
|
+
import { setUpdatedAtValue } from "./instanceProxy.js"
|
|
5
|
+
import { createObjectProxy } from "./objectProxy.js"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export function createNonRelationalArrayProxy(instanceContainingArray, propertyName, array, /**@type {string | undefined}*/ arrElementValidType = undefined, arrayOfObjects = false) {
|
|
9
|
+
const instanceClass = instanceContainingArray.constructor.name
|
|
10
|
+
//if arrElementValidType is undefined, it just means that the array we are proxifying is an array we got from the db, so the typing is correct.
|
|
11
|
+
if (arrElementValidType) array = getValidTypedArray(array, arrElementValidType, false)
|
|
12
|
+
if (arrayOfObjects) {
|
|
13
|
+
for (let i = 0; i < array.length; i++) {
|
|
14
|
+
array[i] = createObjectProxy(instanceContainingArray, propertyName, array[i], arrayOfObjects)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return new Proxy(array, {
|
|
18
|
+
get(target, key, receiver) {
|
|
19
|
+
if (key === "source_") return target
|
|
20
|
+
return target[key]
|
|
21
|
+
},
|
|
22
|
+
set(/**@type {any[]}*/ target, key, value, receiver) {
|
|
23
|
+
if (key === "length") return Reflect.set(target, key, value, receiver)
|
|
24
|
+
return nonRelationalArrayProxySetHandler(target, key, value, propertyName, instanceContainingArray, arrayOfObjects, instanceClass)
|
|
25
|
+
},
|
|
26
|
+
deleteProperty(target, key) {
|
|
27
|
+
return nonRelationalArrayProxyDeleteHandler(target, key, propertyName, instanceContainingArray, instanceClass)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
export function nonRelationalArrayProxyDeleteHandler(array, key, propertyName, instanceContainingArray, instanceClass) {
|
|
34
|
+
const validProp = Object.hasOwn(array, key)
|
|
35
|
+
if (validProp) {
|
|
36
|
+
//@ts-ignore
|
|
37
|
+
const index = parseInt(key)
|
|
38
|
+
if (index > -1) {
|
|
39
|
+
const classChangeObj = OrmStore.getClassChangesObj(instanceClass)
|
|
40
|
+
const instanceChangesObj = classChangeObj[instanceContainingArray.id] ??= {}
|
|
41
|
+
instanceChangesObj[propertyName] = array
|
|
42
|
+
setUpdatedAtValue(instanceContainingArray, instanceChangesObj)
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
delete array[key]
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function nonRelationalArrayProxySetHandler(array, key, value, propertyName, instanceContainingArray, arrayOfObjects, instanceClass) {
|
|
52
|
+
const index = parseInt(key)
|
|
53
|
+
if (index > -1) {
|
|
54
|
+
const classChangeObj = OrmStore.getClassChangesObj(instanceClass)
|
|
55
|
+
const instanceChangesObj = classChangeObj[instanceContainingArray.id] ??= {}
|
|
56
|
+
|
|
57
|
+
if (arrayOfObjects) {
|
|
58
|
+
value = createObjectProxy(instanceContainingArray, propertyName, value, true)
|
|
59
|
+
array[index] = value
|
|
60
|
+
const arrayCopy = []
|
|
61
|
+
array.forEach((proxy, index) => arrayCopy[index] = proxy.source_)
|
|
62
|
+
instanceChangesObj[propertyName] = JSON.stringify(arrayCopy)
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
array[index] = value
|
|
66
|
+
instanceChangesObj[propertyName] = array
|
|
67
|
+
}
|
|
68
|
+
setUpdatedAtValue(instanceContainingArray, instanceChangesObj)
|
|
69
|
+
return true
|
|
70
|
+
}
|
|
71
|
+
else if (array[key]) {
|
|
72
|
+
array[key] = value
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ChangeLogger } from "../changeLogger/changeLogger.js"
|
|
2
|
+
import { OrmStore } from "../misc/ormStore.js"
|
|
3
|
+
import { setUpdatedAtValue } from "./instanceProxy.js"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
function objectProxySetHandler(instanceWithObj, targetProp, isInsideAnArray, className) {
|
|
7
|
+
const classChangeObj = OrmStore.getClassChangesObj(className)
|
|
8
|
+
const instanceId = instanceWithObj.id
|
|
9
|
+
let newPropVal = instanceWithObj[targetProp].source_
|
|
10
|
+
if (isInsideAnArray) newPropVal = JSON.stringify(newPropVal)
|
|
11
|
+
if (classChangeObj[instanceId]) classChangeObj[instanceId][targetProp] = newPropVal
|
|
12
|
+
else classChangeObj[instanceId] = { targetProp: newPropVal }
|
|
13
|
+
setUpdatedAtValue(instanceWithObj, classChangeObj[instanceId])
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createObjectProxy(instanceWithObj, key4object, object, isInsideAnArray = false) {
|
|
18
|
+
const className = instanceWithObj.constructor.name
|
|
19
|
+
|
|
20
|
+
return new Proxy(object, {
|
|
21
|
+
get: (obj, key) => {
|
|
22
|
+
if (key === `source_`) return obj
|
|
23
|
+
return obj[key]
|
|
24
|
+
},
|
|
25
|
+
set: (obj, /**@type {string}*/ key, val) => {
|
|
26
|
+
obj[key] = val
|
|
27
|
+
return objectProxySetHandler(instanceWithObj, key4object, isInsideAnArray, className)
|
|
28
|
+
},
|
|
29
|
+
defineProperty: (obj, /**@type {string}*/ key, val) => {
|
|
30
|
+
obj[key] = val
|
|
31
|
+
return objectProxySetHandler(instanceWithObj, key4object, isInsideAnArray, className)
|
|
32
|
+
},
|
|
33
|
+
deleteProperty(obj, key) {
|
|
34
|
+
const validProp = Object.hasOwn(obj, key)
|
|
35
|
+
const returnedVal = Reflect.deleteProperty(obj, key)
|
|
36
|
+
if (validProp) {
|
|
37
|
+
const classChangeObj = OrmStore.getClassChangesObj(className)
|
|
38
|
+
const instanceChangeObj = classChangeObj[instanceWithObj.id] ??= {}
|
|
39
|
+
let newValueOfTargetProp = isInsideAnArray ? [...instanceWithObj[key4object].source_] : instanceWithObj[key4object].source_
|
|
40
|
+
if (isInsideAnArray) {
|
|
41
|
+
newValueOfTargetProp.forEach((object, index) => newValueOfTargetProp[index] = object.source_)
|
|
42
|
+
newValueOfTargetProp = JSON.stringify(newValueOfTargetProp)
|
|
43
|
+
}
|
|
44
|
+
instanceChangeObj[key4object] = newValueOfTargetProp
|
|
45
|
+
setUpdatedAtValue(instanceWithObj, instanceChangeObj)
|
|
46
|
+
}
|
|
47
|
+
return returnedVal
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
}
|