fixed up qm and added types to BaseHandler for typesafety

This commit is contained in:
Boki 2025-06-28 21:49:33 -04:00
parent 87b1cad4f5
commit 2e86598262
20 changed files with 164 additions and 109 deletions

View file

@ -75,7 +75,7 @@ export async function getChannels(this: CeoHandler, payload: number | undefined)
this.logger.info(`Fetched CEO channels for page ${page}/${totalPages}`);
return { page, totalPages };
} catch (error) {
} catch (error: any) {
this.logger.error(`Error fetching CEO channels for page ${page} with proxy ${proxy}:`, error);
throw new Error(`Failed to fetch CEO channels: ${error.message}`);
}

View file

@ -6,10 +6,11 @@ import {
ScheduledOperation,
} from '@stock-bot/handlers';
import { getChannels, getPosts, getShorts, updateUniqueSymbols } from './actions';
import type { DataIngestionServices } from '../../types';
@Handler('ceo')
@Disabled()
export class CeoHandler extends BaseHandler {
export class CeoHandler extends BaseHandler<DataIngestionServices> {
constructor(services: any) {
super(services); // Handler name read from @Handler decorator
}

View file

@ -1,4 +1,3 @@
import type { IServiceContainer } from '@stock-bot/handlers';
import { fetchExchanges } from './fetch-exchanges.action';
import { fetchSession } from './fetch-session.action';
import { fetchSymbols } from './fetch-symbols.action';

View file

@ -2,10 +2,11 @@
* QM Corporate Actions - Fetch and update dividends, splits, and earnings together
*/
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers';
import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance
let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/**
* Get or initialize the operation tracker
*/
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> {
async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -26,7 +27,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* Single API call returns all three data types
*/
export async function updateCorporateActions(
this: BaseHandler,
this: QMHandler,
input: {
symbol: string;
symbolId: number;
@ -191,7 +192,7 @@ export async function updateCorporateActions(
* Schedule corporate actions updates for symbols that need refreshing
*/
export async function scheduleCorporateActionsUpdates(
this: BaseHandler,
this: QMHandler,
input: {
limit?: number;
forceUpdate?: boolean;

View file

@ -2,10 +2,11 @@
* QM Filings Actions - Fetch and update SEC filings
*/
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers';
import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance
let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/**
* Get or initialize the operation tracker
*/
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> {
async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -25,7 +26,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* Update filings for a single symbol
*/
export async function updateFilings(
this: BaseHandler,
this: QMHandler,
input: {
symbol: string;
symbolId: number;
@ -160,7 +161,7 @@ export async function updateFilings(
* Schedule filings updates for symbols that need refreshing
*/
export async function scheduleFilingsUpdates(
this: BaseHandler,
this: QMHandler,
input: {
limit?: number;
forceUpdate?: boolean;

View file

@ -2,10 +2,11 @@
* QM Financials Actions - Fetch and update financial statements
*/
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers';
import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance
let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/**
* Get or initialize the operation tracker
*/
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> {
async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -25,7 +26,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* Update financials for a single symbol
*/
export async function updateFinancials(
this: BaseHandler,
this: QMHandler,
input: {
symbol: string;
symbolId: number;
@ -136,7 +137,6 @@ export async function updateFinancials(
const tracker = await getOperationTracker(this);
await tracker.updateSymbolOperation(symbol, 'financials_update', {
status: 'failure',
lastRunAt: new Date()
});
return {
@ -151,7 +151,7 @@ export async function updateFinancials(
* Schedule financial updates for symbols that need refreshing
*/
export async function scheduleFinancialsUpdates(
this: BaseHandler,
this: QMHandler,
input: {
limit?: number;
forceUpdate?: boolean;

View file

@ -2,10 +2,11 @@
* QM Intraday Actions - Fetch and update intraday price bars
*/
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers';
import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance
let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/**
* Get or initialize the operation tracker
*/
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> {
async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -26,7 +27,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* This handles both initial crawl and incremental updates
*/
export async function updateIntradayBars(
this: BaseHandler,
this: QMHandler,
input: {
symbol: string;
symbolId: number;
@ -66,7 +67,7 @@ export async function updateIntradayBars(
webmasterId: '500',
date: targetDate.toISOString().split('T')[0],
interval: '1' // 1-minute bars
});
} as Record<string, string>);
// TODO: Update with correct intraday endpoint
const apiUrl = `${QM_CONFIG.BASE_URL}/datatool/intraday.json?${searchParams.toString()}`;
@ -157,7 +158,7 @@ export async function updateIntradayBars(
* This handles both initial crawls and regular updates
*/
export async function scheduleIntradayUpdates(
this: BaseHandler,
this: QMHandler,
input: {
limit?: number;
mode?: 'crawl' | 'update'; // crawl for historical, update for recent
@ -259,7 +260,6 @@ export async function scheduleIntradayUpdates(
crawlState: {
finished: false,
oldestDateReached: new Date(startDate.getTime() - daysToFetch * 24 * 60 * 60 * 1000),
lastCrawlDirection: 'backward'
}
});
} else {

View file

@ -2,10 +2,11 @@
* QM Prices Actions - Fetch and update daily price data
*/
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers';
import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance
let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/**
* Get or initialize the operation tracker
*/
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> {
async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -25,7 +26,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* Update daily prices for a single symbol
*/
export async function updatePrices(
this: BaseHandler,
this: QMHandler,
input: {
symbol: string;
symbolId: number;
@ -164,7 +165,7 @@ export async function updatePrices(
* Schedule price updates for symbols that need refreshing
*/
export async function schedulePriceUpdates(
this: BaseHandler,
this: QMHandler,
input: {
limit?: number;
forceUpdate?: boolean;

View file

@ -2,18 +2,18 @@
* QM Session Actions - Session management and creation
*/
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers';
import { BunRequestInit } from '@stock-bot/utils';
import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { getQmHeaders, QM_CONFIG, QM_SESSION_IDS, SESSION_CONFIG } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMSession } from '../shared/types';
import { type QMSession } from '../shared/types';
/**
* Check existing sessions and queue creation jobs for needed sessions
* This is the main session management function that handles cleanup, maintenance, and initialization
*/
export async function checkSessions(
this: BaseHandler,
this: QMHandler,
_input: unknown,
_context: ExecutionContext
): Promise<{
@ -80,7 +80,7 @@ interface CreateSessionInput {
}
export async function createSession(
this: BaseHandler,
this: QMHandler,
input: CreateSessionInput
): Promise<{ sessionId: string; status: string; sessionType: string, session?: QMSession }> {
const { sessionId, sessionType = 'LOOKUP' } = input || {};
@ -108,7 +108,7 @@ export async function createSession(
const sessionUrl = `${QM_CONFIG.BASE_URL}${QM_CONFIG.SESSION_PATH}/${sessionId}`;
// Build request options
const sessionRequest: BunRequestInit = {
const sessionRequest = {
proxy: proxyUrl || undefined,
headers: getQmHeaders(),
};

View file

@ -2,10 +2,11 @@
* QM Symbol Info Actions - Fetch and update symbol metadata
*/
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers';
import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance
let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/**
* Get or initialize the operation tracker
*/
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> {
async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -26,7 +27,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* This is a simple API fetch operation - no tracking logic here
*/
export async function updateSymbolInfo(
this: BaseHandler,
this: QMHandler,
input: {
symbol: string;
qmSearchCode: string;
@ -144,7 +145,7 @@ export async function updateSymbolInfo(
* This is the scheduled job that finds stale symbols and queues individual updates
*/
export async function scheduleSymbolInfoUpdates(
this: BaseHandler,
this: QMHandler,
input: {
limit?: number;
forceUpdate?: boolean;

View file

@ -2,11 +2,12 @@
* QM Symbol Actions - Symbol search and spider operations
*/
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers';
import type { ExecutionContext } from '@stock-bot/handlers';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
// import { QMOperationTracker } from '../shared/operation-tracker';
// import { initializeQMOperations } from '../shared/operation-registry';
import type { QMHandler } from '../qm.handler';
import type { Exchange, SymbolSpiderJob } from '../shared/types';
/**
@ -15,7 +16,7 @@ import type { Exchange, SymbolSpiderJob } from '../shared/types';
* Each job searches its prefix and creates child jobs if needed
*/
export async function spiderSymbol(
this: BaseHandler,
this: QMHandler,
input: SymbolSpiderJob,
_context: ExecutionContext
): Promise<{
@ -62,7 +63,7 @@ export async function spiderSymbol(
};
}
await this.mongodb.batchUpsert('qmSymbols', symbols, ['qmSearchCode']);
await this.mongodb?.batchUpsert('qmSymbols', symbols, ['qmSearchCode']);
this.logger.info(`Stored symbols from spider search ${prefix} - ${symbols.length}`, {
prefix,
@ -84,7 +85,7 @@ export async function spiderSymbol(
}
if (exchanges.length > 0) {
await this.mongodb.batchUpsert('qmExchanges', exchanges, ['exchange']);
await this.mongodb?.batchUpsert('qmExchanges', exchanges, ['exchange']);
this.logger.debug('Stored exchanges from spider search', {
count: exchanges.length
});
@ -136,7 +137,7 @@ export async function spiderSymbol(
* Search QM symbols API directly
*/
export async function searchSymbols(
this: BaseHandler,
this: QMHandler,
input: { query: string },
_context?: ExecutionContext
): Promise<any[]> {

View file

@ -4,6 +4,7 @@ import {
Operation,
ScheduledOperation,
} from '@stock-bot/handlers';
import type { DataIngestionServices } from '../../types';
import {
checkSessions,
createSession,
@ -25,7 +26,7 @@ import {
import { initializeQMOperations } from './shared/operation-registry';
@Handler('qm')
export class QMHandler extends BaseHandler {
export class QMHandler extends BaseHandler<DataIngestionServices> {
constructor(services: any) {
super(services); // Handler name read from @Handler decorator
// Initialize operations after super() so services are available

View file

@ -3,7 +3,7 @@
* Supports dynamic operation registration with auto-indexing
*/
import type { MongoDBClient, Logger } from '@stock-bot/types';
import type { Logger, MongoDBClient } from '@stock-bot/types';
import type { IntradayCrawlSymbol, QMOperationConfig } from './types';
export class QMOperationTracker {
@ -118,7 +118,7 @@ export class QMOperationTracker {
await this.mongodb.updateOne(this.collectionName, { symbol }, update);
this.logger.trace('Updated symbol operation', {
this.logger.debug('Updated symbol operation', {
symbol,
operation: operationName,
status: data.status

View file

@ -0,0 +1,25 @@
/**
* Data Ingestion Service Types
* Defines required services for data ingestion handlers
*/
import type { ServiceTypes } from '@stock-bot/types';
/**
* Services required by data ingestion handlers
* Makes specific services non-nullable since they're always enabled
*/
export type DataIngestionServices = ServiceTypes & {
// Always required services
logger: NonNullable<ServiceTypes['logger']>;
mongodb: NonNullable<ServiceTypes['mongodb']>;
cache: NonNullable<ServiceTypes['cache']>;
queue: NonNullable<ServiceTypes['queue']>;
queueManager: NonNullable<ServiceTypes['queueManager']>;
browser: NonNullable<ServiceTypes['browser']>;
proxy: NonNullable<ServiceTypes['proxy']>;
// Optional services remain optional
postgres: ServiceTypes['postgres'];
questdb: ServiceTypes['questdb'];
globalCache: ServiceTypes['globalCache'];
};

View file

@ -1,18 +1,18 @@
{
"extends": "../../tsconfig.app.json",
"extends": "../../../tsconfig.app.json",
"references": [
{ "path": "../../libs/core/types" },
{ "path": "../../libs/core/config" },
{ "path": "../../libs/core/logger" },
{ "path": "../../libs/core/di" },
{ "path": "../../libs/core/handlers" },
{ "path": "../../libs/data/cache" },
{ "path": "../../libs/data/mongodb" },
{ "path": "../../libs/data/postgres" },
{ "path": "../../libs/data/questdb" },
{ "path": "../../libs/services/queue" },
{ "path": "../../libs/services/shutdown" },
{ "path": "../../libs/utils" },
{ "path": "../../../libs/core/types" },
{ "path": "../../../libs/core/config" },
{ "path": "../../../libs/core/logger" },
{ "path": "../../../libs/core/di" },
{ "path": "../../../libs/core/handlers" },
{ "path": "../../../libs/core/cache" },
{ "path": "../../../libs/data/mongodb" },
{ "path": "../../../libs/data/postgres" },
{ "path": "../../../libs/data/questdb" },
{ "path": "../../../libs/core/queue" },
{ "path": "../../../libs/core/shutdown" },
{ "path": "../../../libs/utils" },
{ "path": "../config" }
]
}

View file

@ -1,14 +1,14 @@
{
"extends": "../../tsconfig.app.json",
"extends": "../../../tsconfig.app.json",
"references": [
{ "path": "../../libs/core/types" },
{ "path": "../../libs/core/config" },
{ "path": "../../libs/core/logger" },
{ "path": "../../libs/data/cache" },
{ "path": "../../libs/services/queue" },
{ "path": "../../libs/data/mongodb" },
{ "path": "../../libs/data/postgres" },
{ "path": "../../libs/data/questdb" },
{ "path": "../../libs/services/shutdown" }
{ "path": "../../../libs/core/types" },
{ "path": "../../../libs/core/config" },
{ "path": "../../../libs/core/logger" },
{ "path": "../../../libs/core/cache" },
{ "path": "../../../libs/core/queue" },
{ "path": "../../../libs/data/mongodb" },
{ "path": "../../../libs/data/postgres" },
{ "path": "../../../libs/data/questdb" },
{ "path": "../../../libs/core/shutdown" }
]
}

View file

@ -0,0 +1,25 @@
/**
* Web API Service Types
* Defines required services for web API handlers
*/
import type { ServiceTypes } from '@stock-bot/types';
/**
* Services required by web API handlers
* Makes specific services non-nullable since they're always enabled
*/
export type WebApiServices = ServiceTypes & {
// Always required services
logger: NonNullable<ServiceTypes['logger']>;
mongodb: NonNullable<ServiceTypes['mongodb']>;
postgres: NonNullable<ServiceTypes['postgres']>;
cache: NonNullable<ServiceTypes['cache']>;
queue: NonNullable<ServiceTypes['queue']>;
queueManager: NonNullable<ServiceTypes['queueManager']>;
// Optional services remain optional
browser: ServiceTypes['browser'];
proxy: ServiceTypes['proxy'];
questdb: ServiceTypes['questdb'];
globalCache: ServiceTypes['globalCache'];
};

View file

@ -1,14 +1,14 @@
{
"extends": "../../tsconfig.app.json",
"extends": "../../../tsconfig.app.json",
"references": [
{ "path": "../../libs/core/types" },
{ "path": "../../libs/core/config" },
{ "path": "../../libs/core/logger" },
{ "path": "../../libs/data/cache" },
{ "path": "../../libs/services/queue" },
{ "path": "../../libs/data/mongodb" },
{ "path": "../../libs/data/postgres" },
{ "path": "../../libs/data/questdb" },
{ "path": "../../libs/services/shutdown" }
{ "path": "../../../libs/core/types" },
{ "path": "../../../libs/core/config" },
{ "path": "../../../libs/core/logger" },
{ "path": "../../../libs/core/cache" },
{ "path": "../../../libs/core/queue" },
{ "path": "../../../libs/data/mongodb" },
{ "path": "../../../libs/data/postgres" },
{ "path": "../../../libs/data/questdb" },
{ "path": "../../../libs/core/shutdown" }
]
}

View file

@ -40,41 +40,41 @@ export interface JobScheduleOptions {
* Abstract base class for all handlers with improved DI
* Provides common functionality and structure for queue/event operations
*/
export abstract class BaseHandler implements IHandler {
export abstract class BaseHandler<TServices extends ServiceTypes = ServiceTypes> implements IHandler {
// Direct service properties - flattened for cleaner access with proper types
readonly logger: ServiceTypes['logger'];
readonly cache: ServiceTypes['cache'];
readonly globalCache: ServiceTypes['globalCache'];
readonly queueManager: ServiceTypes['queueManager'];
readonly queue: ServiceTypes['queue']; // Specific queue for this handler
readonly proxy: ServiceTypes['proxy'];
readonly browser: ServiceTypes['browser'];
readonly mongodb: ServiceTypes['mongodb'];
readonly postgres: ServiceTypes['postgres'];
readonly questdb: ServiceTypes['questdb'];
readonly logger: TServices['logger'];
readonly cache: TServices['cache'];
readonly globalCache: TServices['globalCache'];
readonly queueManager: TServices['queueManager'];
readonly queue!: TServices['queue']; // Specific queue for this handler - initialized if queueManager exists
readonly proxy: TServices['proxy'];
readonly browser: TServices['browser'];
readonly mongodb: TServices['mongodb'];
readonly postgres: TServices['postgres'];
readonly questdb: TServices['questdb'];
private handlerName: string;
constructor(services: IServiceContainer, handlerName?: string) {
constructor(services: TServices, handlerName?: string) {
// Read handler name from decorator first, then fallback to parameter or class name
const constructor = this.constructor as any;
this.handlerName =
constructor.__handlerName || handlerName || this.constructor.name.toLowerCase();
// Flatten all services onto the handler instance
this.logger = getLogger(this.constructor.name);
this.cache = services.cache;
this.globalCache = services.globalCache;
this.queueManager = services.queueManager;
this.proxy = services.proxy;
this.browser = services.browser;
this.mongodb = services.mongodb;
this.postgres = services.postgres;
this.questdb = services.questdb;
this.logger = getLogger(this.constructor.name) as TServices['logger'];
this.cache = services.cache as TServices['cache'];
this.globalCache = services.globalCache as TServices['globalCache'];
this.queueManager = services.queueManager as TServices['queueManager'];
this.proxy = services.proxy as TServices['proxy'];
this.browser = services.browser as TServices['browser'];
this.mongodb = services.mongodb as TServices['mongodb'];
this.postgres = services.postgres as TServices['postgres'];
this.questdb = services.questdb as TServices['questdb'];
// Get the specific queue for this handler
if (this.queueManager) {
this.queue = this.queueManager.getQueue(this.handlerName);
this.queue = this.queueManager.getQueue(this.handlerName) as TServices['queue'];
}
}

View file

@ -6,12 +6,11 @@
"composite": true,
"incremental": true,
"types": ["bun-types"],
// Modern TC39 Stage 3 decorators (TypeScript 5+ default)
"experimentalDecorators": false,
// Use legacy decorators for compatibility with the handler system
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
// Suppress decorator-related type checking issues due to Bun's hybrid implementation
"skipLibCheck": true,
"suppressImplicitAnyIndexErrors": true
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]