init project starter from nest.js

This commit is contained in:
Rakha adi 2025-10-30 21:11:44 +07:00
parent db23df0fd1
commit 57872f9f1c
34 changed files with 12112 additions and 0 deletions

58
.gitignore vendored Normal file
View File

@ -0,0 +1,58 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
/generated/prisma

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"postman.settings.dotenv-detection-notification-visibility": false
}

35
eslint.config.mjs Normal file
View File

@ -0,0 +1,35 @@
// @ts-check
import eslint from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import globals from 'globals';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{
ignores: ['eslint.config.mjs'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
eslintPluginPrettierRecommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
sourceType: 'commonjs',
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
"prettier/prettier": ["error", { endOfLine: "auto" }],
},
},
);

8
nest-cli.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

11196
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

86
package.json Normal file
View File

@ -0,0 +1,86 @@
{
"name": "hadirapp-backend",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.1",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.1.8",
"@nestjs/swagger": "^11.2.1",
"@prisma/client": "^6.18.0",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"multer": "^2.0.2",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.2.2",
"swagger-ui-express": "^5.0.1",
"uuid": "^13.0.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^22.10.7",
"@types/supertest": "^6.0.2",
"@types/uuid": "^10.0.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^16.0.0",
"jest": "^30.0.0",
"prettier": "^3.4.2",
"prisma": "^6.18.0",
"rxjs": "^7.8.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

12
prisma.config.ts Normal file
View File

@ -0,0 +1,12 @@
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
engine: "classic",
datasource: {
url: env("DATABASE_URL"),
},
});

256
prisma/schema.prisma Normal file
View File

@ -0,0 +1,256 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model admins {
id String @id
userId String @unique
name String
phone String?
createdAt DateTime @default(now())
updatedAt DateTime
users users @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model attendance_sessions {
id String @id
scheduleId String
date DateTime @db.Date
startTime DateTime
endTime DateTime
qrCode String? @unique
qrExpiredAt DateTime?
topic String?
notes String? @db.Text
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime
schedules schedules @relation(fields: [scheduleId], references: [id], onDelete: Cascade)
attendances attendances[]
@@unique([scheduleId, date])
}
model attendances {
id String @id
sessionId String
studentId String
status attendances_status @default(PRESENT)
checkInTime DateTime @default(now())
ipAddress String?
latitude Float?
longitude Float?
deviceInfo String? @db.Text
notes String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime
attendance_sessions attendance_sessions @relation(fields: [sessionId], references: [id], onDelete: Cascade)
students students @relation(fields: [studentId], references: [id], onDelete: Cascade)
@@unique([sessionId, studentId])
@@index([studentId], map: "attendances_studentId_fkey")
}
model audit_logs {
id String @id
userId String
action String
entity String
entityId String
oldValue String? @db.Text
newValue String? @db.Text
ipAddress String?
userAgent String? @db.Text
createdAt DateTime @default(now())
}
model classes {
id String @id
name String @unique
grade String
major String?
capacity Int @default(40)
createdAt DateTime @default(now())
updatedAt DateTime
schedules schedules[]
students students[]
}
model courses {
id String @id
code String @unique
name String
description String? @db.Text
teacherId String
credits Int @default(2)
createdAt DateTime @default(now())
updatedAt DateTime
teachers teachers @relation(fields: [teacherId], references: [id], onDelete: Cascade)
schedules schedules[]
@@index([teacherId], map: "courses_teacherId_fkey")
}
model leave_requests {
id String @id
studentId String
startDate DateTime @db.Date
endDate DateTime @db.Date
type leave_requests_type
reason String @db.Text
attachment String?
status leave_requests_status @default(PENDING)
reviewedBy String?
reviewedAt DateTime?
reviewNotes String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime
students students @relation(fields: [studentId], references: [id], onDelete: Cascade)
@@index([studentId], map: "leave_requests_studentId_fkey")
}
model notifications {
id String @id
userId String
type notifications_type
title String
message String @db.Text
isRead Boolean @default(false)
createdAt DateTime @default(now())
}
model schedules {
id String @id
courseId String
classId String
teacherId String
dayOfWeek schedules_dayOfWeek
startTime String
endTime String
room String?
wifiNetworkId String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime
attendance_sessions attendance_sessions[]
classes classes @relation(fields: [classId], references: [id], onDelete: Cascade)
courses courses @relation(fields: [courseId], references: [id], onDelete: Cascade)
teachers teachers @relation(fields: [teacherId], references: [id], onDelete: Cascade)
wifi_networks wifi_networks? @relation(fields: [wifiNetworkId], references: [id])
@@index([classId], map: "schedules_classId_fkey")
@@index([courseId], map: "schedules_courseId_fkey")
@@index([teacherId], map: "schedules_teacherId_fkey")
@@index([wifiNetworkId], map: "schedules_wifiNetworkId_fkey")
}
model students {
id String @id
userId String @unique
nis String @unique
name String
phone String?
address String? @db.Text
photo String?
classId String?
parentPhone String?
parentEmail String?
createdAt DateTime @default(now())
updatedAt DateTime
attendances attendances[]
leave_requests leave_requests[]
classes classes? @relation(fields: [classId], references: [id])
users users @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([classId], map: "students_classId_fkey")
}
model teachers {
id String @id
userId String @unique
nip String @unique
name String
phone String?
createdAt DateTime @default(now())
updatedAt DateTime
courses courses[]
schedules schedules[]
users users @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model users {
id String @id
email String @unique
password String
role users_role @default(STUDENT)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime
admins admins?
students students?
teachers teachers?
}
model wifi_networks {
id String @id
ssid String
description String?
ipRange String
latitude Float?
longitude Float?
radius Int @default(50)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime
schedules schedules[]
}
enum notifications_type {
ATTENDANCE_REMINDER
ABSENCE_ALERT
LEAVE_APPROVED
LEAVE_REJECTED
SYSTEM
}
enum attendances_status {
PRESENT
LATE
EXCUSED
SICK
ABSENT
}
enum users_role {
ADMIN
STUDENT
TEACHER
}
enum leave_requests_type {
SICK
EXCUSED
OTHER
}
enum schedules_dayOfWeek {
MONDAY
TUESDAY
WEDNESDAY
THURSDAY
FRIDAY
SATURDAY
SUNDAY
}
enum leave_requests_status {
PENDING
APPROVED
REJECTED
}

View File

@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getRoot()).toBe('{ message: \'HadirApp Backend is running 🚀\' }');
});
});
});

9
src/app.controller.ts Normal file
View File

@ -0,0 +1,9 @@
import { Controller, Get } from '@nestjs/common';
@Controller()
export class AppController {
@Get()
getRoot() {
return { message: 'HadirApp Backend is running 🚀' };
}
}

17
src/app.module.ts Normal file
View File

@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AuthModule } from './modules/auth/auth.module';
import { UsersModule } from './modules/users/users.module';
import { PrismaModule } from './modules/prisma/prisma.module';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
PrismaModule,
AuthModule,
UsersModule,
],
controllers: [AppController],
})
export class AppModule {}

8
src/app.service.ts Normal file
View File

@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

View File

@ -0,0 +1,4 @@
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

View File

@ -0,0 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

View File

@ -0,0 +1,20 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.includes(user.role);
}
}

22
src/main.ts Normal file
View File

@ -0,0 +1,22 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
const config = new DocumentBuilder()
.setTitle('HadirApp API')
.setDescription('API documentation for attendance system')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
await app.listen(process.env.PORT || 3000);
}
bootstrap();

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from './auth.controller';
describe('AuthController', () => {
let controller: AuthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
}).compile();
controller = module.get<AuthController>(AuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,44 @@
import { Body, Controller, Post } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBody, ApiResponse, ApiProperty } from '@nestjs/swagger';
import { AuthService } from './auth.service';
class RegisterDto {
@ApiProperty({ example: 'user@example.com', description: 'User email' })
email: string;
@ApiProperty({ example: 'strongPassword123', description: 'Plain text password' })
password: string;
@ApiProperty({ example: 'STUDENT', description: 'Role: ADMIN | STUDENT | TEACHER', required: false })
role?: string;
}
class LoginDto {
@ApiProperty({ example: 'user@example.com' })
email: string;
@ApiProperty({ example: 'strongPassword123' })
password: string;
}
@ApiTags('auth')
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('register')
@ApiOperation({ summary: 'Register a new user' })
@ApiBody({ type: RegisterDto })
@ApiResponse({ status: 201, description: 'User registered successfully' })
async register(@Body() body: RegisterDto) {
return this.authService.register(body.email, body.password, body.role || 'STUDENT');
}
@Post('login')
@ApiOperation({ summary: 'Authenticate user and return JWT' })
@ApiBody({ type: LoginDto })
@ApiResponse({ status: 200, description: 'Login successful, returns access_token' })
async login(@Body() body: LoginDto) {
return this.authService.login(body.email, body.password);
}
}

View File

@ -0,0 +1,23 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module';
import { PrismaModule } from '../prisma/prisma.module';
import { JwtStrategy } from './strategies/jwt.strategy';
@Module({
imports: [
UsersModule,
PrismaModule,
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET || 'supersecretkey',
signOptions: { expiresIn: '1d' },
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,55 @@
import { Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { Prisma, users_role } from '@prisma/client';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class AuthService {
constructor(private prisma: PrismaService, private jwt: JwtService) {}
async register(email: string, password: string, role?: string) {
// validate inputs to avoid passing undefined to bcrypt/prisma
if (!email || !password) {
throw new BadRequestException('Email and password are required');
}
if (typeof password !== 'string' || password.length < 6) {
throw new BadRequestException('Password must be a string with at least 6 characters');
}
// pastikan role sesuai enum di database (uppercase)
const allowedRoles = ['ADMIN', 'STUDENT', 'TEACHER'];
const normalizedRole = (role || 'STUDENT').toUpperCase();
if (!allowedRoles.includes(normalizedRole)) {
throw new BadRequestException(`Invalid role. Allowed: ${allowedRoles.join(', ')}`);
}
const hashed = await bcrypt.hash(password, 10);
const user = await this.prisma.users.create({
data: { id: uuidv4(), email, password: hashed, role: normalizedRole as users_role, updatedAt: new Date() },
});
return { message: 'User registered', user };
}
async login(email: string, password: string) {
// validate inputs to avoid calling Prisma with undefined values
if (!email || !password) {
throw new BadRequestException('Email and password are required');
}
const user = await this.prisma.users.findUnique({ where: { email } });
if (!user) throw new UnauthorizedException('User not found');
const valid = await bcrypt.compare(password, user.password);
if (!valid) throw new UnauthorizedException('Invalid credentials');
const token = await this.jwt.signAsync({
sub: user.id,
email: user.email,
role: user.role,
});
return { message: 'Login success', access_token: token };
}
}

View File

@ -0,0 +1,18 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET || 'supersecretkey',
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email, role: payload.role };
}
}

View File

@ -0,0 +1,9 @@
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}

View File

@ -0,0 +1,13 @@
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
describe('UsersController', () => {
let controller: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
}).compile();
controller = module.get<UsersController>(UsersController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,23 @@
import { Controller, Get, Param, UseGuards } from '@nestjs/common';
import { UsersService } from './users.service';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RolesGuard } from '../../common/guards/roles.guard';
import { Roles } from '../../common/decorators/roles.decorator';
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard)
export class UsersController {
constructor(private usersService: UsersService) {}
@Get()
@Roles('ADMIN')
async getAll() {
return this.usersService.findAll();
}
@Get(':id')
@Roles('ADMIN', 'TEACHER')
async getOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
}

View File

@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [UsersService],
controllers: [UsersController],
exports: [UsersService],
})
export class UsersModule {}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,15 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async findAll() {
return this.prisma.users.findMany();
}
async findOne(id: string) {
return this.prisma.users.findUnique({ where: { id } });
}
}

25
test/app.e2e-spec.ts Normal file
View File

@ -0,0 +1,25 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { App } from 'supertest/types';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication<App>;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

9
test/jest-e2e.json Normal file
View File

@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

4
tsconfig.build.json Normal file
View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

25
tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"resolvePackageJsonExports": true,
"esModuleInterop": true,
"isolatedModules": true,
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2023",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./src",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false
}
}