All files / src db.ts

61.42% Statements 86/140
100% Branches 0/0
0% Functions 0/4
61.42% Lines 86/140

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 1411x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                                             1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x       1x 1x 1x 1x 1x 1x        
 
 
import { type ModelsWithDbNamesAndReadWrite, type MainDbName, type AllDbIds, type DbIds, defaultDbName } from './cache/dbs/index.generated'
import { error } from './error'
import { getMainConfig, getDbConfigs } from './helpers/getGreenDotConfigs'
import { DaoMethodsMongo } from './databases/mongo/types/mongoDaoTypes'
import { ModelAdditionalFields, ModelsConfigCache, mongoInitDb } from './databases/mongo/initMongoDb'
import { DefinitionObjChild, ModelReadWrite } from 'good-cop'
import { C, objEntries, timeout } from 'topkat-utils'
import { getProjectDatabaseDaosForDbName, getProjectDatabaseModelsForDbName } from './helpers/getProjectModelsAndDaos'
 
import { GD_serverBlacklistModel } from './security/userAndConnexion/GD_serverBlackList.model'
import { GD_deviceModel } from './security/userAndConnexion/GD_device.model'
import { dbIdsToDbNames } from './databases/dbIdsToDbNames'
import { InferTypeRead, InferTypeWrite } from 'good-cop'
 
type InferTypeRW<T extends DefinitionObjChild> = { Read: InferTypeRead<T>, Write: InferTypeWrite<T> }
 
//  ╔══╗ ╔══╗ ╔══╗ ╦  ╦ ╔══╗
//  ║    ╠══╣ ║    ╠══╣ ╠═
//  ╚══╝ ╩  ╩ ╚══╝ ╩  ╩ ╚══╝
//
//----------------------------------------
// CACHE HANDLING
//- - - - - - - - - - - - - - - - - - - -
// Cache is here to prevent a database initializing twice, and to
// refresh database initialization if needed, for example
// to take in account new DBs that may have been created since last
// server start
//----------------------------------------
export const dbCache = {} as ModelsConfigCache
 
//  ══╦══ ╦   ╦ ╔══╗ ╔══╗ ╔═══
//    ║   ╚═╦═╝ ╠══╝ ╠═   ╚══╗
//    ╩     ╩   ╩    ╚══╝ ═══╝
//
// Types needs are computed appart from logic in this case
 
export type Dbs = {
  [K in keyof ModelsWithDbNamesAndReadWrite]: {
    [L in keyof ModelsWithDbNamesAndReadWrite[K]]:
    L extends 'GD_serverBlackList' ? DaoMethodsMongo<InferTypeRW<typeof GD_serverBlacklistModel>> :
    L extends 'GD_device' ? DaoMethodsMongo<InferTypeRW<typeof GD_deviceModel>> :
    ModelsWithDbNamesAndReadWrite[K][L] extends ModelReadWrite ? DaoMethodsMongo<ModelsWithDbNamesAndReadWrite[K][L]> : never
  } & ModelAdditionalFields
}
 
export type Db = Dbs[MainDbName]
 
 
//  ═╦═ ╦╗ ╔ ═╦═ ══╦══   ╔═╗  ╔═╗  ╔═══
//   ║  ║╚╗║  ║    ║     ║  ║ ╠═╩╗ ╚══╗
//  ═╩═ ╩ ╚╩ ═╩═   ╩     ╚══╝ ╚══╝ ═══╝
 
let isRunning = false
 
 
export async function initDbs(resetCache: boolean = false) {

  if (isRunning) {
    await timeout(2000)
    return C.warning(false, 'initDbCore() is called twice while in progress of being initiated')
  } else isRunning = true

  const dbConfigs = getDbConfigs()

  for (const { dbs: connexionConfigs, name: dbName, type } of dbConfigs) {

    const models = await getProjectDatabaseModelsForDbName(dbName, resetCache)
    const daos = await getProjectDatabaseDaosForDbName(dbName, resetCache)


    if (type === 'mongo') {
      //----------------------------------------
      // DATABASES INITIALISATION
      //----------------------------------------
      const { connexionString, ...conf } = connexionConfigs

      const connexionObj = typeof connexionString === 'string' ? { [dbName]: connexionString } : connexionString

      for (const [dbId, mongoConStr] of objEntries(connexionObj)) {

        dbIdsToDbNames[dbId] = dbName

        if (dbCache?.[dbId]?.dbConfigs) continue // even when clearing cache, you don't want to reinit projects

        await mongoInitDb(
          dbName as keyof DbIds,
          dbId as AllDbIds,
          dbCache,
          { ...conf, connexionString: mongoConStr },
          daos,
          models
        )
      }
    } else {
      throw error.serverError(`Database type not implemented: ${type}. Please make sure you provided gd.config.ts a defaultDatabase`, { dbName: dbName, dbType: type })
    }
  }

  isRunning = false

  C.log(C.primary(`✓ DB Initialized`))
}
 
 
//  ╔═╗  ╔═╗
//  ║  ║ ╠═╩╗
//  ╚══╝ ╚══╝
 
/** Use that in your backend app has the main DB entry point of any database operations.
 * @example ```db.myDbName.myModelName.count(ctx, { status: 'success' })```
 */
export const dbs = new Proxy({} as Dbs, {
  // proxy pattern here is a workaround to ensure user always get the latest db cache version
  // on server start, we need to await initDb to ensure the cache always has a value and can
  // be called anywhere in the app
  get(_, prop: string) {
    if (!dbCache[prop]?.db) throw C.error('DBs not initialized, run "await initDb()" once before calling getDb()')
    return dbCache[prop].db
  },
})
 
export const db = new Proxy({} as Db, {
  // we also use proxy here for we can use getGreenDotConfigSync() without instanciating it
  // once in the file and thus wait until db and cache are operational
  // In short we make sync out of async (more DX friendly at usage)
  get(_, prop: string) {
    if (!dbCache[defaultDbName]?.db) throw C.error('DB not initialized, run "await initDb()" once before calling getDb()')
    if (!dbCache[defaultDbName]?.db?.[prop]) throw C.error(`Model ${prop} doesn't seem to be properly initialized and is not defined in modelsCache`)
    return dbCache[defaultDbName].db[prop]
  },
})
 
 
 
/** This mean to exist for performances reason to avoid initiating two databases when there is two execution contexts (the client app / the green_dot module), TODO In Progress not fully tested / implemented */
export function updateCacheFromOutside(cache2: ModelsConfigCache) {
  Object.assign(dbCache, cache2)
}