Initial Commit

This commit is contained in:
2025-08-18 17:18:43 -04:00
commit 1bc3aaa8aa
17 changed files with 373 additions and 0 deletions

34
.gitignore vendored Normal file
View File

@@ -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

3
.npmrc Normal file
View File

@@ -0,0 +1,3 @@
@techniker-me:registry=https://registry-node.techniker.me
package-lock=false
save-exact=true

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
20

15
README.md Normal file
View File

@@ -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.

5
bunfig.toml Normal file
View File

@@ -0,0 +1,5 @@
[install.lockfile]
save = false
[install.scopes]
"@techniker-me" = "https://registry-node.techniker.me"

View File

@@ -0,0 +1,3 @@
export default function assertUnreachable(value: never): never {
throw new Error(`Unreachable code: ${value}`);
}

16
package.json Normal file
View File

@@ -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"
}
}

View File

@@ -0,0 +1,5 @@
export default class CommandLine {
private constructor() {
throw new Error('[CommandLine] is a static class that may not be instantiated');
}
}

View File

View File

@@ -0,0 +1,4 @@
import PCastAPI from '@techniker-me/pcast-api';
import RtmpPush from '@technniker-me/rtmp-push';
class TestRunner {}

120
test/logger/Logger.ts Normal file
View File

@@ -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);
}
}

View File

@@ -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<string, Logger> = 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');
}
}

View File

@@ -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}]`);
}
}
}

13
test/logger/Threshold.ts Normal file
View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,7 @@
import {LoggingLevel} from '../LoggingLevel';
interface IAppender {
append(timestamp: string, level: LoggingLevel, category: string, message: string): void;
}
export default IAppender;

29
tsconfig.json Normal file
View File

@@ -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
}
}