init
This commit is contained in:
commit
502420a1b2
94
.gitignore
vendored
Normal file
94
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Dependencies
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Dependency directories
|
||||
jspm_packages/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
|
||||
# Storybook build outputs
|
||||
.out
|
||||
.storybook-out
|
||||
|
||||
# Temporary folders
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
105
README.md
Normal file
105
README.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# SolPay - Node.js TypeScript Application
|
||||
|
||||
A modern Node.js application built with TypeScript, featuring Express.js for the web server and comprehensive testing setup.
|
||||
|
||||
## Features
|
||||
|
||||
- 🚀 **TypeScript** - Full TypeScript support with strict type checking
|
||||
- 🧪 **Testing** - Jest testing framework with TypeScript support
|
||||
- 🔧 **Development** - Hot reload with ts-node for development
|
||||
- 📦 **Build System** - TypeScript compiler with source maps
|
||||
- 🎯 **Modern ES2020** - Latest JavaScript features
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js (v16 or higher)
|
||||
- npm or yarn
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd solpay
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Available Scripts
|
||||
|
||||
- **`npm run dev`** - Start development server with hot reload
|
||||
- **`npm run build`** - Build the TypeScript code to JavaScript
|
||||
- **`npm start`** - Start the production server (requires build first)
|
||||
- **`npm run watch`** - Watch for changes and rebuild automatically
|
||||
- **`npm test`** - Run tests
|
||||
- **`npm run clean`** - Clean build output
|
||||
|
||||
## Development
|
||||
|
||||
To start developing:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This will start the server using ts-node, which automatically compiles TypeScript on the fly.
|
||||
|
||||
## Building for Production
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
solpay/
|
||||
├── src/ # TypeScript source code
|
||||
├── dist/ # Compiled JavaScript (generated)
|
||||
├── tests/ # Test files
|
||||
├── package.json # Dependencies and scripts
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
├── jest.config.js # Jest testing configuration
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run tests with:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
Run tests with coverage:
|
||||
|
||||
```bash
|
||||
npm test -- --coverage
|
||||
```
|
||||
|
||||
## TypeScript Configuration
|
||||
|
||||
The project uses strict TypeScript settings for better code quality:
|
||||
|
||||
- Strict mode enabled
|
||||
- No implicit any types
|
||||
- Source maps for debugging
|
||||
- Declaration files generation
|
||||
- ES2020 target
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Add tests for new functionality
|
||||
5. Ensure all tests pass
|
||||
6. Submit a pull request
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see LICENSE file for details
|
||||
15
jest.config.js
Normal file
15
jest.config.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/src'],
|
||||
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest',
|
||||
},
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.ts',
|
||||
'!src/**/*.d.ts',
|
||||
],
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'lcov', 'html'],
|
||||
};
|
||||
5534
package-lock.json
generated
Normal file
5534
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
35
package.json
Normal file
35
package.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "solpay",
|
||||
"version": "1.0.0",
|
||||
"description": "A Node.js TypeScript application",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "ts-node src/index.ts",
|
||||
"watch": "tsc --watch",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "jest"
|
||||
},
|
||||
"keywords": [
|
||||
"nodejs",
|
||||
"typescript",
|
||||
"solpay"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/jest": "^29.5.8",
|
||||
"@types/node": "^20.10.0",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-node": "^10.9.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@solana/web3.js": "^1.98.4",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
}
|
||||
77
src/__tests__/config.test.ts
Normal file
77
src/__tests__/config.test.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { config } from '../config';
|
||||
|
||||
describe('Config', () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
process.env = { ...originalEnv };
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('port configuration', () => {
|
||||
it('should use default port 3000 when PORT is not set', () => {
|
||||
delete process.env.PORT;
|
||||
const { config: testConfig } = require('../config');
|
||||
expect(testConfig.port).toBe(3000);
|
||||
});
|
||||
|
||||
it('should use PORT environment variable when set', () => {
|
||||
process.env.PORT = '8080';
|
||||
const { config: testConfig } = require('../config');
|
||||
expect(testConfig.port).toBe(8080);
|
||||
});
|
||||
|
||||
it('should parse string port to number', () => {
|
||||
process.env.PORT = '5000';
|
||||
const { config: testConfig } = require('../config');
|
||||
expect(typeof testConfig.port).toBe('number');
|
||||
expect(testConfig.port).toBe(5000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nodeEnv configuration', () => {
|
||||
it('should use default nodeEnv "development" when NODE_ENV is not set', () => {
|
||||
delete process.env.NODE_ENV;
|
||||
const { config: testConfig } = require('../config');
|
||||
expect(testConfig.nodeEnv).toBe('development');
|
||||
});
|
||||
|
||||
it('should use NODE_ENV environment variable when set', () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
const { config: testConfig } = require('../config');
|
||||
expect(testConfig.nodeEnv).toBe('production');
|
||||
});
|
||||
});
|
||||
|
||||
describe('logLevel configuration', () => {
|
||||
it('should use default logLevel "info" when LOG_LEVEL is not set', () => {
|
||||
delete process.env.LOG_LEVEL;
|
||||
const { config: testConfig } = require('../config');
|
||||
expect(testConfig.logLevel).toBe('info');
|
||||
});
|
||||
|
||||
it('should use LOG_LEVEL environment variable when set', () => {
|
||||
process.env.LOG_LEVEL = 'debug';
|
||||
const { config: testConfig } = require('../config');
|
||||
expect(testConfig.logLevel).toBe('debug');
|
||||
});
|
||||
});
|
||||
|
||||
describe('config interface', () => {
|
||||
it('should have all required properties', () => {
|
||||
expect(config).toHaveProperty('port');
|
||||
expect(config).toHaveProperty('nodeEnv');
|
||||
expect(config).toHaveProperty('logLevel');
|
||||
});
|
||||
|
||||
it('should have correct types', () => {
|
||||
expect(typeof config.port).toBe('number');
|
||||
expect(typeof config.nodeEnv).toBe('string');
|
||||
expect(typeof config.logLevel).toBe('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
3
src/__tests__/explorer.test.ts
Normal file
3
src/__tests__/explorer.test.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
//spl token transfer tx : 2Dq3cW3D7z75QNfjiTfDu3GgS2gaqrqmoaqzDoomTkJqT2ThSyNDNf1tTz8SgXmzSon9FHCyN61EFkAdkJquhbJf
|
||||
//sol transfer tx : 3c6AiQmRxnAKSQyQ3C6jPoEmhGPHieQ2uJWuBQjdMA2TPXMWeGtL6PmdUGrMdojFoazq1NUQnDb9qCq1o4sxYvZ8
|
||||
|
||||
85
src/__tests__/logger.test.ts
Normal file
85
src/__tests__/logger.test.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { Logger } from '../utils/logger';
|
||||
|
||||
describe('Logger', () => {
|
||||
let logger: Logger;
|
||||
let consoleSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
logger = new Logger('debug');
|
||||
consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should set default log level to info when no level provided', () => {
|
||||
const defaultLogger = new Logger();
|
||||
expect(defaultLogger).toBeInstanceOf(Logger);
|
||||
});
|
||||
|
||||
it('should parse log level correctly', () => {
|
||||
const debugLogger = new Logger('debug');
|
||||
const infoLogger = new Logger('info');
|
||||
const warnLogger = new Logger('warn');
|
||||
const errorLogger = new Logger('error');
|
||||
|
||||
expect(debugLogger).toBeInstanceOf(Logger);
|
||||
expect(infoLogger).toBeInstanceOf(Logger);
|
||||
expect(warnLogger).toBeInstanceOf(Logger);
|
||||
expect(errorLogger).toBeInstanceOf(Logger);
|
||||
});
|
||||
});
|
||||
|
||||
describe('info method', () => {
|
||||
it('should log info messages when log level is info or lower', () => {
|
||||
logger.info('Test info message');
|
||||
expect(consoleSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not log info messages when log level is warn or higher', () => {
|
||||
const warnLogger = new Logger('warn');
|
||||
warnLogger.info('Test info message');
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('debug method', () => {
|
||||
it('should log debug messages when log level is debug', () => {
|
||||
logger.debug('Test debug message');
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not log debug messages when log level is info or higher', () => {
|
||||
const infoLogger = new Logger('info');
|
||||
infoLogger.debug('Test debug message');
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('warn method', () => {
|
||||
it('should log warn messages when log level is warn or lower', () => {
|
||||
logger.warn('Test warn message');
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('error method', () => {
|
||||
it('should log error messages when log level is error or lower', () => {
|
||||
logger.error('Test error message');
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('context logging', () => {
|
||||
it('should include context in log messages', () => {
|
||||
const context = { userId: '123', action: 'login' };
|
||||
logger.info('User action', context);
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('User action')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
31
src/config/index.ts
Normal file
31
src/config/index.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import dotenv from 'dotenv';
|
||||
import { Environment } from '../types';
|
||||
|
||||
// Load environment variables from .env file
|
||||
dotenv.config();
|
||||
|
||||
export interface Config {
|
||||
port: number;
|
||||
nodeEnv: Environment;
|
||||
logLevel: 'error' | 'warn' | 'info' | 'debug';
|
||||
}
|
||||
|
||||
// Validate and export environment configuration
|
||||
export const config: Config = {
|
||||
port: Number(process.env.PORT) || 3000,
|
||||
nodeEnv: (process.env.NODE_ENV as Environment) || 'development',
|
||||
logLevel: (process.env.LOG_LEVEL as Config['logLevel']) || 'info'
|
||||
};
|
||||
|
||||
// Validate required environment variables
|
||||
if (!config.port || isNaN(config.port)) {
|
||||
throw new Error('Invalid PORT configuration');
|
||||
}
|
||||
|
||||
if (!['development', 'staging', 'production'].includes(config.nodeEnv)) {
|
||||
throw new Error('Invalid NODE_ENV configuration');
|
||||
}
|
||||
|
||||
if (!['error', 'warn', 'info', 'debug'].includes(config.logLevel)) {
|
||||
throw new Error('Invalid LOG_LEVEL configuration');
|
||||
}
|
||||
7
src/data/index.ts
Normal file
7
src/data/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { clusterApiUrl } from "@solana/web3.js";
|
||||
import { config } from "../config";
|
||||
|
||||
const CLUSTER_API_PROD = clusterApiUrl("mainnet-beta");
|
||||
const CLUSTER_API_DEV = clusterApiUrl("devnet");
|
||||
|
||||
export const CLUSTER_API = config.nodeEnv === "production" ? CLUSTER_API_PROD : CLUSTER_API_DEV;
|
||||
46
src/index.ts
Normal file
46
src/index.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import express, { Request, Response } from 'express';
|
||||
import { config } from './config';
|
||||
import { logger } from './utils/logger';
|
||||
import { getTransaction } from './utils/explorer';
|
||||
logger.info("Starting server...");
|
||||
const app = express();
|
||||
const PORT = config.port;
|
||||
logger.info(`port: ${PORT}`);
|
||||
logger.info(`nodeEnv: ${config.nodeEnv}`);
|
||||
logger.info(`logLevel: ${config.logLevel}`);
|
||||
|
||||
// Middleware
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Basic route
|
||||
app.get('/', (_req: Request, res: Response) => {
|
||||
res.json({
|
||||
message: 'Welcome to SolPay API',
|
||||
version: '1.0.0',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (_req: Request, res: Response) => {
|
||||
res.json({
|
||||
status: 'OK',
|
||||
uptime: process.uptime(),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/tx/:txHash', async (req: Request, res: Response) => {
|
||||
const txHash = req.params.txHash;
|
||||
const transaction = await getTransaction(txHash);
|
||||
res.json(transaction);
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(PORT, () => {
|
||||
logger.info(`🚀 Server is running on port ${PORT}`);
|
||||
logger.info(`📖 API documentation available at http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
export default app;
|
||||
67
src/types/index.ts
Normal file
67
src/types/index.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// API Response types
|
||||
export interface ApiResponse<T = any> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
message?: string;
|
||||
error?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
// Request types
|
||||
export interface PaginationQuery {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
// Error types
|
||||
export interface AppError extends Error {
|
||||
statusCode: number;
|
||||
isOperational: boolean;
|
||||
}
|
||||
|
||||
// User types (example)
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
// Validation types
|
||||
export interface ValidationError {
|
||||
field: string;
|
||||
message: string;
|
||||
value?: any;
|
||||
}
|
||||
|
||||
// Environment types
|
||||
export type Environment = 'development' | 'staging' | 'production';
|
||||
|
||||
// Database types (example)
|
||||
export interface DatabaseConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
database: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface TransferData {
|
||||
sender: string;
|
||||
receiver: string;
|
||||
amount: number;
|
||||
mint: string;
|
||||
time: number;
|
||||
blockhash: string;
|
||||
}
|
||||
|
||||
86
src/utils/explorer.ts
Normal file
86
src/utils/explorer.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { CLUSTER_API } from "../data";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { TransferData } from "../types";
|
||||
|
||||
export async function getTransaction(txHash: string) {
|
||||
const connection = new Connection(CLUSTER_API);
|
||||
|
||||
const transaction = await connection.getParsedTransaction(txHash, {
|
||||
maxSupportedTransactionVersion: 0,
|
||||
});
|
||||
|
||||
const transaction_formatted = extractTokenTransferData(transaction);
|
||||
return transaction_formatted;
|
||||
}
|
||||
|
||||
export function extractNativeSolTransferData(transaction: any): TransferData | null {
|
||||
try {
|
||||
// Check if this is a native SOL transfer
|
||||
const instructions = transaction.transaction.message.instructions;
|
||||
const systemTransferInstruction = instructions.find((instruction: any) =>
|
||||
instruction.program === "system" && instruction.parsed?.type === "transfer"
|
||||
);
|
||||
|
||||
if (!systemTransferInstruction) {
|
||||
return null; // Not a native SOL transfer
|
||||
}
|
||||
|
||||
const transferInfo = systemTransferInstruction.parsed.info;
|
||||
|
||||
return {
|
||||
sender: transferInfo.source,
|
||||
receiver: transferInfo.destination,
|
||||
amount: transferInfo.lamports,
|
||||
mint: "So11111111111111111111111111111111111111111", // Native SOL mint address
|
||||
time: transaction.blockTime,
|
||||
blockhash: transaction.transaction.message.recentBlockhash
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error extracting native SOL transfer data:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function extractSplTokenTransferData(transaction: any): TransferData | null {
|
||||
try {
|
||||
// Check if this is an SPL token transfer
|
||||
const instructions = transaction.transaction.message.instructions;
|
||||
const tokenTransferInstruction = instructions.find((instruction: any) =>
|
||||
instruction.program === "spl-token" && instruction.parsed?.type === "transferChecked"
|
||||
);
|
||||
|
||||
if (!tokenTransferInstruction) {
|
||||
return null; // Not an SPL token transfer
|
||||
}
|
||||
|
||||
const transferInfo = tokenTransferInstruction.parsed.info;
|
||||
|
||||
return {
|
||||
sender: transferInfo.source,
|
||||
receiver: transferInfo.destination,
|
||||
amount: parseInt(transferInfo.tokenAmount.amount),
|
||||
mint: transferInfo.mint,
|
||||
time: transaction.blockTime,
|
||||
blockhash: transaction.transaction.message.recentBlockhash
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error extracting SPL token transfer data:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function extractTokenTransferData(transaction: any): TransferData | null {
|
||||
// Try to extract native SOL transfer first
|
||||
const nativeSolData = extractNativeSolTransferData(transaction);
|
||||
if (nativeSolData) {
|
||||
return nativeSolData;
|
||||
}
|
||||
|
||||
// Try to extract SPL token transfer
|
||||
const splTokenData = extractSplTokenTransferData(transaction);
|
||||
if (splTokenData) {
|
||||
return splTokenData;
|
||||
}
|
||||
|
||||
return null; // Not a recognized token transfer
|
||||
}
|
||||
74
src/utils/logger.ts
Normal file
74
src/utils/logger.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
export enum LogLevel {
|
||||
ERROR = 'error',
|
||||
WARN = 'warn',
|
||||
INFO = 'info',
|
||||
DEBUG = 'debug'
|
||||
}
|
||||
|
||||
export interface LogMessage {
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
context?: Record<string, any>;
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
private logLevel: LogLevel;
|
||||
|
||||
constructor(logLevel: string = 'info') {
|
||||
this.logLevel = this.parseLogLevel(logLevel);
|
||||
}
|
||||
|
||||
private parseLogLevel(level: string): LogLevel {
|
||||
switch (level.toLowerCase()) {
|
||||
case 'error': return LogLevel.ERROR;
|
||||
case 'warn': return LogLevel.WARN;
|
||||
case 'info': return LogLevel.INFO;
|
||||
case 'debug': return LogLevel.DEBUG;
|
||||
default: return LogLevel.INFO;
|
||||
}
|
||||
}
|
||||
|
||||
private shouldLog(level: LogLevel): boolean {
|
||||
const levels = [LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO, LogLevel.DEBUG];
|
||||
return levels.indexOf(level) <= levels.indexOf(this.logLevel);
|
||||
}
|
||||
|
||||
private formatMessage(level: LogLevel, message: string, context?: Record<string, any>): string {
|
||||
const timestamp = new Date().toISOString();
|
||||
const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
|
||||
|
||||
if (context) {
|
||||
return `${prefix} ${message} ${JSON.stringify(context)}`;
|
||||
}
|
||||
|
||||
return `${prefix} ${message}`;
|
||||
}
|
||||
|
||||
error(message: string, context?: Record<string, any>): void {
|
||||
if (this.shouldLog(LogLevel.ERROR)) {
|
||||
console.error(this.formatMessage(LogLevel.ERROR, message, context));
|
||||
}
|
||||
}
|
||||
|
||||
warn(message: string, context?: Record<string, any>): void {
|
||||
if (this.shouldLog(LogLevel.WARN)) {
|
||||
console.warn(this.formatMessage(LogLevel.WARN, message, context));
|
||||
}
|
||||
}
|
||||
|
||||
info(message: string, context?: Record<string, any>): void {
|
||||
if (this.shouldLog(LogLevel.INFO)) {
|
||||
console.info(this.formatMessage(LogLevel.INFO, message, context));
|
||||
}
|
||||
}
|
||||
|
||||
debug(message: string, context?: Record<string, any>): void {
|
||||
if (this.shouldLog(LogLevel.DEBUG)) {
|
||||
console.debug(this.formatMessage(LogLevel.DEBUG, message, context));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create default logger instance
|
||||
export const logger = new Logger(process.env.LOG_LEVEL);
|
||||
31
tsconfig.json
Normal file
31
tsconfig.json
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"removeComments": false,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*.test.ts"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user