diff --git a/src/data-source/index.ts b/src/data-source/index.ts index ba58edda..41cd1a3b 100644 --- a/src/data-source/index.ts +++ b/src/data-source/index.ts @@ -1,3 +1,4 @@ export * from './options'; export * from './find'; export * from './singleton'; +export * from './type'; diff --git a/src/data-source/type.ts b/src/data-source/type.ts new file mode 100644 index 00000000..21052714 --- /dev/null +++ b/src/data-source/type.ts @@ -0,0 +1,3 @@ +import type { DataSourceOptions } from 'typeorm'; + +export type DataSourceCacheOption = DataSourceOptions['cache']; diff --git a/src/env/constants.ts b/src/env/constants.ts index 1b2dbcad..8537ac94 100644 --- a/src/env/constants.ts +++ b/src/env/constants.ts @@ -10,3 +10,99 @@ export enum EnvironmentName { PRODUCTION = 'production', TEST = 'test', } + +export enum EnvironmentVariableName { + ENV = 'NODE_ENV', + + // Seeder + SEEDS = 'DB_SEEDS', + SEEDS_ALT = 'TYPEORM_SEEDING_SEEDS', + + FACTORIES = 'DB_FACTORIES', + FACTORIES_ALT = 'TYPEORM_SEEDING_FACTORIES', + + // Database + CONNECTION = 'DB_CONNECTION', + CONNECTION_ALT = 'TYPEORM_CONNECTION', + + URL = 'DB_URL', + URL_ALT = 'TYPEORM_URL', + + HOST = 'DB_HOST', + HOST_ALT = 'TYPEORM_HOST', + + PORT = 'DB_PORT', + PORT_ALT = 'TYPEORM_PORT', + + USERNAME = 'DB_USERNAME', + USERNAME_ALT = 'TYPEORM_USERNAME', + + PASSWORD = 'DB_PASSWORD', + PASSWORD_ALT = 'TYPEORM_PASSWORD', + + DATABASE = 'DB_DATABASE', + DATABASE_ALT = 'TYPEORM_DATABASE', + + SID = 'DB_SID', + SID_ALT = 'TYPEORM_SID', + + SCHEMA = 'DB_SCHEMA', + SCHEMA_ALT = 'TYPEORM_SCHEMA', + + SCHEMA_DROP = 'DB_DROP_SCHEMA', + SCHEMA_DROP_ALT = 'TYPEORM_DROP_SCHEMA', + + DRIVER_EXTRA = 'DB_DRIVER_EXTRA', + DRIVER_EXTRA_ALT = 'TYPEORM_DRIVER_EXTRA', + + SYNCHRONIZE = 'TYPEORM_SYNCHRONIZE', + SYNCHRONIZE_ALT = 'TYPEORM_SYNCHRONIZE', + + MIGRATIONS = 'DB_MIGRATIONS', + MIGRATIONS_ALT = 'TYPEORM_MIGRATIONS', + + MIGRATIONS_RUN = 'DB_MIGRATIONS_RUN', + MIGRATIONS_RUN_ALT = 'TYPEORM_MIGRATIONS_RUN', + + MIGRATIONS_TABLE_NAME = 'DB_MIGRATIONS_TABLE_NAME', + MIGRATIONS_TABLE_NAME_ALT = 'TYPEORM_MIGRATIONS_TABLE_NAME', + + ENTITIES = 'TYPEORM_ENTITIES', + ENTITIES_ALT = 'TYPEORM_ENTITIES', + + ENTITY_PREFIX = 'DB_ENTITY_PREFIX', + ENTITY_PREFIX_ALT = 'TYPEORM_ENTITY_PREFIX', + + METADATA_TABLE_NAME = 'DB_METADATA_TABLE_NAME', + METADATA_TABLE_NAME_ALT = 'TYPEORM_METADATA_TABLE_NAME', + + SUBSCRIBERS = 'DB_SUBSCRIBERS', + SUBSCRIBERS_ALT = 'TYPEORM_SUBSCRIBERS', + + LOGGING = 'DB_LOGGING', + LOGGING_ALT = 'TYPEORM_LOGGING', + + LOGGER = 'DB_LOGGER', + LOGGER_ALT = 'TYPEORM_LOGGER', + + MAX_QUERY_EXECUTION_TIME = 'DB_MAX_QUERY_EXECUTION_TIME', + MAX_QUERY_EXECUTION_TIME_ALT = 'TYPEORM_MAX_QUERY_EXECUTION_TIME', + + DEBUG = 'DB_DEBUG', + DEBUG_ALT = 'TYPEORM_DEBUG', + + UUID_EXTENSION = 'DB_UUID_EXTENSION', + UUID_EXTENSION_ALT = 'TYPEORM_UUID_EXTENSION', + + CACHE = 'DB_CACHE', + CACHE_ALT = 'TYPEORM_CACHE', + + CACHE_ALWAYS_ENABLED = 'DB_CACHE_ALWAYS_ENABLED', + CACHE_ALWAYS_ENABLED_ALT = 'TYPEORM_CACHE_ALWAYS_ENABLED', + + CACHE_OPTIONS = 'DB_CACHE_OPTIONS', + CACHE_OPTIONS_ALT = 'TYPEORM_CACHE_OPTIONS', + + CACHE_DURATION = 'DB_CACHE_DURATION', + CACHE_DURATION_ALT = 'TYPEORM_CACHE_DURATION', +} diff --git a/src/env/module.ts b/src/env/module.ts index 0483b975..e29661c5 100644 --- a/src/env/module.ts +++ b/src/env/module.ts @@ -1,6 +1,14 @@ -import type { EnvironmentName } from './constants'; +import { EnvironmentName, EnvironmentVariableName } from './constants'; import type { Environment } from './type'; -import { hasProcessEnv, readFromProcessEnv } from './utils'; +import { + hasProcessEnv, + readBoolFromProcessEnv, + readFromProcessEnv, + readIntFromProcessEnv, + transformCache, + transformLogging, + transformStringToArray, +} from './utils'; let instance : Environment | undefined; @@ -15,34 +23,142 @@ export function useEnv(key?: string) : any { return instance; } - const seeds : string[] = []; - const seedKeys = ['DATABASE_SEEDER_SEEDS', 'TYPEORM_SEEDING_SEEDS']; - for (let i = 0; i < seedKeys.length; i++) { - if (!hasProcessEnv(seedKeys[i])) { - continue; - } + const output: Environment = { + env: readFromProcessEnv(EnvironmentVariableName.ENV, EnvironmentName.DEVELOPMENT) as `${EnvironmentName}`, + + // Seeder + seeds: transformStringToArray(readFromProcessEnv([ + EnvironmentVariableName.SEEDS, + EnvironmentVariableName.SEEDS_ALT, + ])), + factories: transformStringToArray(readFromProcessEnv([ + EnvironmentVariableName.FACTORIES, + EnvironmentVariableName.FACTORIES_ALT, + ])), + + // Database + url: readFromProcessEnv([ + EnvironmentVariableName.URL, + EnvironmentVariableName.URL_ALT, + ]), + host: readFromProcessEnv([ + EnvironmentVariableName.HOST, + EnvironmentVariableName.HOST_ALT, + ]), + port: readIntFromProcessEnv([ + EnvironmentVariableName.PORT, + EnvironmentVariableName.PORT_ALT, + ]), + username: readFromProcessEnv([ + EnvironmentVariableName.USERNAME, + EnvironmentVariableName.USERNAME_ALT, + ]), + password: readFromProcessEnv([ + EnvironmentVariableName.PASSWORD, + EnvironmentVariableName.PASSWORD_ALT, + ]), + database: readFromProcessEnv([ + EnvironmentVariableName.DATABASE, + EnvironmentVariableName.DATABASE_ALT, + ]), + sid: readFromProcessEnv([ + EnvironmentVariableName.SID, + EnvironmentVariableName.SID_ALT, + ]), + schema: readFromProcessEnv([ + EnvironmentVariableName.SCHEMA, + EnvironmentVariableName.SCHEMA_ALT, + ]), + extra: readFromProcessEnv([ + EnvironmentVariableName.DRIVER_EXTRA, + EnvironmentVariableName.DRIVER_EXTRA_ALT, + ]), + synchronize: readBoolFromProcessEnv([ + EnvironmentVariableName.SYNCHRONIZE, + EnvironmentVariableName.SYNCHRONIZE_ALT, + ]), + schemaDrop: readBoolFromProcessEnv([ + EnvironmentVariableName.SCHEMA_DROP, + EnvironmentVariableName.SCHEMA_DROP_ALT, + ]), + migrationsRun: readBoolFromProcessEnv([ + EnvironmentVariableName.MIGRATIONS_RUN, + EnvironmentVariableName.MIGRATIONS_RUN_ALT, + ]), + entities: transformStringToArray(readFromProcessEnv([ + EnvironmentVariableName.ENTITIES, + EnvironmentVariableName.ENTITIES_ALT])), + migrations: transformStringToArray(readFromProcessEnv([ + EnvironmentVariableName.MIGRATIONS, + EnvironmentVariableName.MIGRATIONS_ALT])), + migrationsTableName: readFromProcessEnv([ + EnvironmentVariableName.MIGRATIONS_TABLE_NAME, + EnvironmentVariableName.MIGRATIONS_TABLE_NAME_ALT, + ]), + metadataTableName: readFromProcessEnv([ + EnvironmentVariableName.METADATA_TABLE_NAME, + EnvironmentVariableName.METADATA_TABLE_NAME_ALT, + ]), + subscribers: transformStringToArray(readFromProcessEnv([ + EnvironmentVariableName.SUBSCRIBERS, + EnvironmentVariableName.SUBSCRIBERS_ALT, + ])), + logging: transformLogging(readFromProcessEnv([ + EnvironmentVariableName.LOGGING, + EnvironmentVariableName.LOGGING_ALT, + ])), + logger: readFromProcessEnv([ + EnvironmentVariableName.LOGGER, + EnvironmentVariableName.LOGGER_ALT, + ]), + entityPrefix: readFromProcessEnv([ + EnvironmentVariableName.ENTITY_PREFIX, + EnvironmentVariableName.ENTITY_PREFIX_ALT, + ]), + maxQueryExecutionTime: readFromProcessEnv([ + EnvironmentVariableName.MAX_QUERY_EXECUTION_TIME, + EnvironmentVariableName.MAX_QUERY_EXECUTION_TIME_ALT, + ]), + debug: readFromProcessEnv([ + EnvironmentVariableName.DEBUG, + EnvironmentVariableName.DEBUG_ALT, + ]), + cache: transformCache(readFromProcessEnv([ + EnvironmentVariableName.CACHE, + EnvironmentVariableName.CACHE_ALT, + ])), + uuidExtension: readFromProcessEnv([ + EnvironmentVariableName.UUID_EXTENSION, + EnvironmentVariableName.UUID_EXTENSION_ALT, + ]), + + }; - seeds.push(...readFromProcessEnv(seedKeys[i], '').split(',')); + if (output.extra) { + output.extra = JSON.parse(output.extra); // todo: ensure record ?? } - const factories : string[] = []; - const factoryKeys = ['DATABASE_SEEDER_FACTORIES', 'TYPEORM_SEEDING_FACTORIES']; - for (let i = 0; i < factoryKeys.length; i++) { - if (!hasProcessEnv(factoryKeys[i])) { - continue; + let type : string | undefined; + if (hasProcessEnv([EnvironmentVariableName.CONNECTION, EnvironmentVariableName.CONNECTION_ALT])) { + type = readFromProcessEnv([EnvironmentVariableName.CONNECTION, EnvironmentVariableName.CONNECTION_ALT]); + } else if (hasProcessEnv([EnvironmentVariableName.URL, EnvironmentVariableName.URL_ALT])) { + const temp = readFromProcessEnv([EnvironmentVariableName.URL, EnvironmentVariableName.URL_ALT]); + if (temp) { + const parts = temp.split('://'); + if (parts.length > 0) { + // eslint-disable-next-line prefer-destructuring + type = parts[0]; + } } - - factories.push(...readFromProcessEnv(factoryKeys[i], '').split(',')); + } + if (type) { + output.type = type; } - instance = { - env: readFromProcessEnv('NODE_ENV', 'development') as `${EnvironmentName}`, - seeds: seeds.filter((el) => el), - factories: factories.filter((el) => el), - }; + instance = output; if (typeof key === 'string') { - return instance[key as keyof Environment]; + return output[key as keyof Environment]; } return instance; diff --git a/src/env/type.ts b/src/env/type.ts index 668055c7..4fa8b165 100644 --- a/src/env/type.ts +++ b/src/env/type.ts @@ -1,7 +1,40 @@ +import type { DataSourceCacheOption } from '../data-source'; import type { EnvironmentName } from './constants'; export interface Environment { env: `${EnvironmentName}`, + + // Seeder seeds: string[], - factories: string[] + factories: string[], + + // DataSource + type?: string, + url?: string, + host?: string, + port?: number, + username?: string, + password?: string, + database?: string, + sid?: string, // string ?? + schema?: string, // string ?? + /** + * dropSchema + */ + schemaDrop?: boolean, + extra?: any, + synchronize?: boolean, + migrations: string[], + migrationsRun?: boolean, + migrationsTableName?: string, + entities: string[], + entityPrefix?: string, + metadataTableName?: string, + subscribers: string[], + logging: string[] | boolean | string, + logger?: string, + maxQueryExecutionTime?: string, + debug?: string, + cache?: DataSourceCacheOption, + uuidExtension?: string } diff --git a/src/env/utils.ts b/src/env/utils.ts index cfe61e84..a1eff63b 100644 --- a/src/env/utils.ts +++ b/src/env/utils.ts @@ -1,16 +1,131 @@ import process from 'node:process'; +import type { DataSourceCacheOption } from '../data-source'; import { hasOwnProperty } from '../utils'; +import { EnvironmentVariableName } from './constants'; -export function hasProcessEnv(key: string) : boolean { - return hasOwnProperty(process.env, key); +export function hasProcessEnv(key: string | string[]) : boolean { + const keys = Array.isArray(key) ? key : [key]; + for (let i = 0; i < keys.length; i++) { + if (hasOwnProperty(process.env, keys[i])) { + return true; + } + } + return false; +} + +export function readFromProcessEnv(key: string | string[]) : string | undefined; +export function readFromProcessEnv(key: string | string[], alt: T) : T | string; +export function readFromProcessEnv(key: string | string[], alt?: T): any { + const keys = Array.isArray(key) ? key : [key]; + for (let i = 0; i < keys.length; i++) { + if (hasOwnProperty(process.env, keys[i])) { + return process.env[keys[i]]; + } + } + + return alt; +} + +export function readIntFromProcessEnv( + key: string | string[], + alt?: number, +): number | undefined { + const keys = Array.isArray(key) ? key : [key]; + + for (let i = 0; i < keys.length; i++) { + const value = readFromProcessEnv(keys[i], alt); + const intValue = parseInt(`${value}`, 10); + + if (!Number.isNaN(intValue)) { + return intValue; + } + } + + return alt; +} + +function extractBooleanFromString(input?: string | boolean) : boolean | undefined { + switch (input) { + case true: + case 'true': + case 't': + case '1': + return true; + case false: + case 'false': + case 'f': + case '0': + return false; + } + + return undefined; } -export function readFromProcessEnv(key: string) : string | undefined; -export function readFromProcessEnv(key: string, alt: T) : T | string; -export function readFromProcessEnv(key: string, alt?: T): any { - if (hasOwnProperty(process.env, key)) { - return process.env[key]; +export function readBoolFromProcessEnv(key: string | string[], alt?: boolean): boolean | undefined { + const keys = Array.isArray(key) ? key : [key]; + for (let i = 0; i < keys.length; i++) { + const value = extractBooleanFromString(readFromProcessEnv(keys[i], alt)); + if (typeof value === 'boolean') { + return value; + } } return alt; } + +export function transformStringToArray(input?: string) : string[] { + if (!input) { + return []; + } + + return input.split(',').map((el) => el.trim()); +} + +export function transformLogging(input?: string) : boolean | string | string[] { + const value = extractBooleanFromString(input); + if (typeof value === 'boolean') { + return value; + } + + if (value === 'all') { + return 'all'; + } + + return transformStringToArray(value); +} + +export function transformCache(input?: string) : DataSourceCacheOption | undefined { + const value = extractBooleanFromString(input); + if (typeof value === 'boolean') { + return value; + } + + if ( + input === 'redis' || + input === 'ioredis' || + input === 'database' || + input === 'ioredis/cluster' + ) { + let options : Record | undefined; + if (hasProcessEnv([EnvironmentVariableName.CACHE_OPTIONS, EnvironmentVariableName.CACHE_OPTIONS_ALT])) { + const temp = readFromProcessEnv([EnvironmentVariableName.CACHE_OPTIONS, EnvironmentVariableName.CACHE_OPTIONS_ALT]); + if (temp) { + options = JSON.parse(temp); + } + } + return { + type: input, + options, + alwaysEnabled: readBoolFromProcessEnv([ + EnvironmentVariableName.CACHE_ALWAYS_ENABLED, + EnvironmentVariableName.CACHE_ALWAYS_ENABLED_ALT, + ]), + duration: readIntFromProcessEnv([ + EnvironmentVariableName.CACHE_DURATION, + EnvironmentVariableName.CACHE_DURATION_ALT, + ]), + }; + } + + return undefined; +}