commit 1bc3aaa8aa6fe22607a877157ef900d097fd2fcb Author: Alexander Zinn Date: Mon Aug 18 17:18:43 2025 -0400 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..056a250 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +@techniker-me:registry=https://registry-node.techniker.me +package-lock=false +save-exact=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3c1c71 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# channeltests-3 + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.2.20. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..ae22d90 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,5 @@ +[install.lockfile] +save = false + +[install.scopes] +"@techniker-me" = "https://registry-node.techniker.me" diff --git a/lang/assertUnreachable.ts b/lang/assertUnreachable.ts new file mode 100644 index 0000000..e61b3b6 --- /dev/null +++ b/lang/assertUnreachable.ts @@ -0,0 +1,3 @@ +export default function assertUnreachable(value: never): never { + throw new Error(`Unreachable code: ${value}`); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4bdb177 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "channeltests-3", + "version": "2025.0.0", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "5.9.8" + }, + "dependencies": { + "@techniker-me/pcast-api": "2025.0.2", + "@techniker-me/rtmp-push": "2025.0.2" + } +} diff --git a/test/config/CommandLine.ts b/test/config/CommandLine.ts new file mode 100644 index 0000000..eacd618 --- /dev/null +++ b/test/config/CommandLine.ts @@ -0,0 +1,5 @@ +export default class CommandLine { + private constructor() { + throw new Error('[CommandLine] is a static class that may not be instantiated'); + } +} diff --git a/test/config/TestConfiguration.ts b/test/config/TestConfiguration.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/config/TestRunner.ts b/test/config/TestRunner.ts new file mode 100644 index 0000000..630924f --- /dev/null +++ b/test/config/TestRunner.ts @@ -0,0 +1,4 @@ +import PCastAPI from '@techniker-me/pcast-api'; +import RtmpPush from '@technniker-me/rtmp-push'; + +class TestRunner {} diff --git a/test/logger/Logger.ts b/test/logger/Logger.ts new file mode 100644 index 0000000..e339caf --- /dev/null +++ b/test/logger/Logger.ts @@ -0,0 +1,120 @@ +import IAppender from './appenders/IAppender'; +import {LoggingLevel, LoggingLevelMapping} from './LoggingLevel'; +import type {Threshold} from './Threshold'; + +export class Logger { + private readonly _category: string; + private readonly _threshold: Threshold; + private readonly _appenders: IAppender[]; + + constructor({category, threshold, appenders}: {category: string; threshold: Threshold; appenders: IAppender[]}) { + this._category = category; + this._threshold = threshold; + this._appenders = appenders; + } + + public info(message: string, ...optionalParameters: unknown[]): void { + if (this._threshold.value < LoggingLevel.Info) { + return; + } + + this.log(LoggingLevel.Info, message, ...optionalParameters); + } + + public warn(message: string, ...optionalParameters: unknown[]): void { + if (this._threshold.value < LoggingLevel.Warning) { + return; + } + + this.log(LoggingLevel.Warning, message, ...optionalParameters); + } + + public error(message: string, ...optionalParameters: unknown[]): void { + if (this._threshold.value < LoggingLevel.Error) { + return; + } + + this.log(LoggingLevel.Error, message, ...optionalParameters); + } + + public fatal(message: string, ...optionalParameters: unknown[]): void { + if (this._threshold.value < LoggingLevel.Fatal) { + return; + } + + this.log(LoggingLevel.Fatal, message, ...optionalParameters); + } + + public debug(message: string, ...optionalParameters: unknown[]): void { + if (this._threshold.value < LoggingLevel.Debug) { + return; + } + + this.log(LoggingLevel.Debug, message, ...optionalParameters); + } + + public trace(message: string, ...optionalParameters: unknown[]): void { + if (this._threshold.value < LoggingLevel.Trace) { + return; + } + + this.log(LoggingLevel.Trace, message, ...optionalParameters); + } + + private log(level: LoggingLevel, message: string, ...optionalParameters: unknown[]): void { + const timestamp = new Date().toISOString(); + const formattedMessage = this.formatMessage(message, ...optionalParameters); + const levelString = LoggingLevelMapping.convertLoggingLevelToLoggingLevelType(level); + + // Safely call appenders with error handling + this._appenders.forEach(appender => { + try { + appender.append(timestamp, level, this._category, `[${levelString}] ${formattedMessage}`); + } catch (error) { + // Fallback to console if appender fails + console.error(`[Logger] Appender failed for category ${this._category}:`, error); + console.log(`${timestamp} [${this._category}] [${levelString}] ${formattedMessage}`); + } + }); + } + + private formatMessage(message: string, ...optionalParameters: unknown[]): string { + if (optionalParameters.length === 0) { + return message; + } + + // More efficient parameter substitution + let result = message; + let paramIndex = 0; + + // Replace all {} placeholders with parameters + while (result.includes('{}') && paramIndex < optionalParameters.length) { + const paramString = this.parameterToString(optionalParameters[paramIndex]); + result = result.replace('{}', paramString); + paramIndex++; + } + + // Append remaining parameters if any + if (paramIndex < optionalParameters.length) { + const remainingParams = optionalParameters.slice(paramIndex) + .map(param => this.parameterToString(param)) + .join(' '); + result += ` ${remainingParams}`; + } + + return result; + } + + private parameterToString(param: unknown): string { + if (param === null) return 'null'; + if (param === undefined) return 'undefined'; + if (typeof param === 'object') { + try { + return JSON.stringify(param); + } catch { + return String(param); + } + } + return String(param); + } +} diff --git a/test/logger/LoggerFactory.ts b/test/logger/LoggerFactory.ts new file mode 100644 index 0000000..1f3d63b --- /dev/null +++ b/test/logger/LoggerFactory.ts @@ -0,0 +1,27 @@ +import { ConsoleAppender } from './appenders/ConsoleAppender'; +import IAppender from './appenders/IAppender'; +import {Logger} from './Logger'; +import {LoggingLevel} from './LoggingLevel'; +import {Threshold} from './Threshold'; + +export default class LoggerFactory { + private static readonly _loggerForCategory: Map = new Map(); + private static readonly _threshold: Threshold = new Threshold({level: LoggingLevel.Debug}); + private static readonly _appenders: IAppender[] = [new ConsoleAppender()]; + + public static getLogger(category: string): Logger { + let logger = LoggerFactory._loggerForCategory.get(category); + + if (logger === undefined) { + logger = new Logger({category, threshold: LoggerFactory._threshold, appenders: LoggerFactory._appenders}); + + LoggerFactory._loggerForCategory.set(category, logger); + } + + return logger; + } + + private constructor() { + throw new Error('[LoggerFactory] is a static class that may not be instantiated'); + } +} \ No newline at end of file diff --git a/test/logger/LoggingLevel.ts b/test/logger/LoggingLevel.ts new file mode 100644 index 0000000..bf0d7d4 --- /dev/null +++ b/test/logger/LoggingLevel.ts @@ -0,0 +1,60 @@ +export enum LoggingLevel { + Off = 0, + Fatal = 1, + Error = 2, + Warning = 3, + Info = 4, + Debug = 5, + Trace = 6, + All = 7 +} + +export type LoggingLevelType = 'Off' | 'Fatal' | 'Error' | 'Warning' | 'Info' | 'Debug' | 'Trace' | 'All'; + +export class LoggingLevelMapping { + public static convertLoggingLevelToLoggingLevelType(level: LoggingLevel): LoggingLevelType { + switch (level) { + case LoggingLevel.Off: + return 'Off'; + case LoggingLevel.Fatal: + return 'Fatal'; + case LoggingLevel.Error: + return 'Error'; + case LoggingLevel.Warning: + return 'Warning'; + case LoggingLevel.Info: + return 'Info'; + case LoggingLevel.Debug: + return 'Debug'; + case LoggingLevel.Trace: + return 'Trace'; + case LoggingLevel.All: + return 'All'; + default: + throw new Error(`[LoggingLevelMapping] Received unknown logging level [${level}]`); + } + } + + public static convertLoggingLevelTypeToLoggingLevel(level: LoggingLevelType): LoggingLevel { + switch (level) { + case 'Off': + return LoggingLevel.Off; + case 'Fatal': + return LoggingLevel.Fatal; + case 'Error': + return LoggingLevel.Error; + case 'Warning': + return LoggingLevel.Warning; + case 'Info': + return LoggingLevel.Info; + case 'Debug': + return LoggingLevel.Debug; + case 'Trace': + return LoggingLevel.Trace; + case 'All': + return LoggingLevel.All; + default: + throw new Error(`[LoggingLevelMapping] Received unknown logging level type [${level}]`); + } + } +} diff --git a/test/logger/Threshold.ts b/test/logger/Threshold.ts new file mode 100644 index 0000000..c3b8692 --- /dev/null +++ b/test/logger/Threshold.ts @@ -0,0 +1,13 @@ +import {LoggingLevel, LoggingLevelMapping, type LoggingLevelType} from './LoggingLevel'; + +export class Threshold { + private readonly _level: {value: LoggingLevel}; + + constructor({level}: {level: LoggingLevel}) { + this._level = {value: level}; + } + + get value(): LoggingLevel { + return this._level.value; + } +} diff --git a/test/logger/appenders/ConsoleAppender.ts b/test/logger/appenders/ConsoleAppender.ts new file mode 100644 index 0000000..98fe9d2 --- /dev/null +++ b/test/logger/appenders/ConsoleAppender.ts @@ -0,0 +1,31 @@ +import IAppender from './IAppender'; +import {LoggingLevel} from '../LoggingLevel'; +import assertUnreachable from '../../../lang/assertUnreachable'; + +export class ConsoleAppender implements IAppender { + append(timestamp: string, level: LoggingLevel, category: string, message: string): void { + switch (level) { + case LoggingLevel.Debug: + case LoggingLevel.Info: + case LoggingLevel.Warning: + case LoggingLevel.Trace: + console.info(`${timestamp} [${category}] ${message}`); + break; + + case LoggingLevel.Error: + console.error(`${timestamp} [${category}] ${message}`); + break; + + case LoggingLevel.Fatal: + console.error(`${timestamp} [${category}] ${message}`); + break; + + case LoggingLevel.Off: + case LoggingLevel.All: + break; + + default: + assertUnreachable(level); + } + } +} diff --git a/test/logger/appenders/IAppender.ts b/test/logger/appenders/IAppender.ts new file mode 100644 index 0000000..c2152f2 --- /dev/null +++ b/test/logger/appenders/IAppender.ts @@ -0,0 +1,7 @@ +import {LoggingLevel} from '../LoggingLevel'; + +interface IAppender { + append(timestamp: string, level: LoggingLevel, category: string, message: string): void; +} + +export default IAppender; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d23842c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": false, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}