import { registerCacheEngineCreator } from "@mediaurl/cache" ; import { BasicCache } from "@mediaurl/cache/dist/engines/basic" ; import retryPromise from "promise-retry" ; import { Connection , ConnectionOptions , createConnection , LessThan , } from "typeorm" ; import { URL } from "url" ; import { CacheItem } from "./CacheItem" ; type CreateOptions = Partial < ConnectionOptions > ; const checkTtl = ( cacheResult ) => { if ( ! cacheResult ) { return ; } if ( cacheResult . d && cacheResult . d < + new Date ( ) ) { return ; } return cacheResult ; } ; const getKey = ( key ) => key . substring ( 1 ) ; const primaryKey = "k" ; export class SqlCache extends BasicCache { private connectionP : Promise < Connection > ; constructor ( opts ) { super ( ) ; this . connectionP = retryPromise ( ( retry ) => { return createConnection ( { name : `sql-cache-${ opts . type }` , ... opts , synchronize : true , entities : [ CacheItem ] , } as ConnectionOptions ) . catch ( retry ) ; } , { minTimeout : 100 , randomize : true , } ) ; } async exists ( key ) { const c = await this . connectionP ; return c . getRepository ( CacheItem ) . findOne ( { k : getKey ( key ) } ) . then ( checkTtl ) . then ( ( _ ) => ! ! _ ) ; } async get ( key ) { const c = await this . connectionP ; return c . getRepository ( CacheItem ) . findOne ( { k : getKey ( key ) , } ) . then ( checkTtl ) . then ( ( cacheResult ) => { return cacheResult ?. v ; } ) ; } async set ( key , value , ttl ) { const c = await this . connectionP ; const item = new CacheItem ( ) ; item . k = getKey ( key ) ; item . v = value ; item . d = ttl === Infinity ? null : + new Date ( Date . now ( ) + ttl ) ; const updateKeys = Object . keys ( item ) . filter ( ( _ ) => _ !== primaryKey ) ; const qb = c . createQueryBuilder ( ) . insert ( ) . into ( CacheItem ) . values ( item ) . orUpdate ( { columns : updateKeys , conflict_target : [ `"${ primaryKey }"` ] , } ) ; updateKeys . forEach ( ( key ) => { qb . setParameter ( key , typeof item [ key ] === "object" && item [ key ] !== null ? JSON . stringify ( item [ key ] ) : item [ key ] ) ; } ) ; await qb . execute ( ) ; return ; } async delete ( key ) { const c = await this . connectionP ; await c . getRepository ( CacheItem ) . delete ( { k : getKey ( key ) } ) ; } async deleteAll ( ) { const c = await this . connectionP ; await c . getRepository ( CacheItem ) . delete ( { } ) ; } public async cleanup ( ) { const c = await this . connectionP ; await c . getRepository ( CacheItem ) . delete ( { d : LessThan ( + new Date ( ) ) , } ) ; } } const allowedTypes = [ "postgres" , "mysql" ] ; export const getTypeFromUrl = ( url ) => { const type = < CreateOptions [ "type" ] > new URL ( url ) . protocol . replace ( /:$/ , "" ) ; if ( ! allowedTypes . includes ( type ) ) throw new Error ( `SQL engine "${ type }" not available, use one of ${ allowedTypes . join ( ", " ) }` ) ; return type ; } ; export const createFromUrl = ( url , opts ? ) => { const type = getTypeFromUrl ( url ) ; return new SqlCache ( < CreateOptions > { type , url , ... opts } ) ; } ; registerCacheEngineCreator ( ( ) => { const url = process . env . SQL_CACHE_URL ; return url ? createFromUrl ( url ) : null ; } ) ;