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,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
+ }