diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..b6369400 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +packages/db/src/entities/*.ts \ No newline at end of file diff --git a/.gitignore b/.gitignore index 04be68c3..f839c16a 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ yarn-error.log* # local env files **/.env +**/.db.env **/.env*.local # vercel @@ -43,4 +44,5 @@ next-env.d.ts amplifyconfiguration.json #databases -packages/server/database.sqlite \ No newline at end of file +packages/server/database.sqlite +packages/db/dist \ No newline at end of file diff --git a/docs/AMPLIFY.md b/docs/AMPLIFY.md index 8b1ebade..11632848 100644 --- a/docs/AMPLIFY.md +++ b/docs/AMPLIFY.md @@ -205,3 +205,11 @@ This table stores the [packet](./TELEMETRY.md#packet) data. The timestamp of whe } } ``` + +### Connecting through the instance through SSM + +You need this IAM Role to be able to connect through SSM + +``` +AmazonEC2RoleforSSM +``` diff --git a/package.json b/package.json index 8d4edab1..993891f3 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,23 @@ "dev:server": "lerna run dev --scope=server --stream", "build:client": "lerna run build --scope=client --stream", "build:server": "lerna run build --scope=server --stream", + "build:db": "lerna run build --scope=db --stream", "start:client": "lerna run start --scope=client --stream", "start:server": "lerna run start --scope=server --stream", + "start:db": "lerna run start --scope=db --stream", "lint": "lerna run lint", "lintf": "lerna run lintf", - "test": "lerna run test" + "test": "lerna run test", + "db:up": "lerna run db:up --scope=db", + "db:down": "lerna run db:down --scope=db", + "db:logs": "lerna run db:logs --scope=db", + "db:build": "lerna run build --scope=db", + "migrate:generate": "lerna run migrate:generate --scope=db --", + "migrate:run": "lerna run migrate:run --scope=db", + "migrate:revert": "lerna run migrate:revert --scope=db", + "migrate:reset": "lerna run migrate:reset --scope=db", + "schema:sync": "lerna run schema:sync --scope=db", + "schema:drop": "lerna run schema:drop --scope=db" }, "dependencies": { "@mantine/dates": "^8.0.1", diff --git a/packages/db/.env.example b/packages/db/.env.example new file mode 100644 index 00000000..017d25ca --- /dev/null +++ b/packages/db/.env.example @@ -0,0 +1,6 @@ +POSTGRES_PASSWORD=postgres +POSTGRES_USER=postgres +DB_NAME=postgres +DB_HOST=localhost +DB_PORT=5432 +NODE_ENV=development \ No newline at end of file diff --git a/packages/db/README.md b/packages/db/README.md new file mode 100644 index 00000000..c1c1fd51 --- /dev/null +++ b/packages/db/README.md @@ -0,0 +1,8 @@ +# Node TypeORM Example + +Example of using TimescaleDB with Node.js and [TypeORM](https://typeorm.io/). + +```bash +docker compose down -v # take down volumes as well + +``` diff --git a/packages/db/docker-compose.yml b/packages/db/docker-compose.yml index d857e41f..d2d6d671 100644 --- a/packages/db/docker-compose.yml +++ b/packages/db/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.9" services: db: - image: timescale/timescaledb-ha:pg17 # Image for timescale db with postgres 17 + image: timescale/timescaledb:latest-pg17 # Image for timescale db with postgres 17 env_file: - .db.env # contains the information regarding our user, password, and database ports: diff --git a/packages/db/package.json b/packages/db/package.json new file mode 100644 index 00000000..723ac5cd --- /dev/null +++ b/packages/db/package.json @@ -0,0 +1,35 @@ +{ + "name": "db", + "version": "0.1.0", + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "build": "tsc", + "start": "tsx src/index.ts", + "test": "jest --runInBand", + "db:up": "docker-compose up -d", + "db:down": "docker-compose down", + "db:logs": "docker-compose logs -f timescaledb", + "migrate:generate": "typeorm-ts-node-commonjs migration:generate -d src/data-source.ts", + "migrate:run": "typeorm-ts-node-commonjs migration:run -d src/data-source.ts", + "migrate:revert": "typeorm-ts-node-commonjs migration:revert -d src/data-source.ts", + "migrate:reset": "typeorm-ts-node-commonjs migration:revert -d src/data-source.ts && typeorm-ts-node-commonjs migration:run -d src/data-source.ts", + "schema:sync": "typeorm-ts-node-commonjs schema:sync -d src/data-source.ts", + "schema:drop": "typeorm-ts-node-commonjs schema:drop -d src/data-source.ts" + }, + "dependencies": { + "@timescaledb/core": "^0.0.1", + "@timescaledb/schemas": "^0.0.1", + "@timescaledb/typeorm": "^0.0.1", + "dotenv": "^16.4.5", + "express": "^4.18.2", + "pg": "^8.11.3", + "reflect-metadata": "^0.1.13", + "typeorm": "^0.3.20" + }, + "devDependencies": { + "@faker-js/faker": "^9.3.0", + "@types/pg": "^8.11.0", + "typescript": "^5.5.3" + } +} diff --git a/packages/db/src/data-source.ts b/packages/db/src/data-source.ts new file mode 100644 index 00000000..68f283af --- /dev/null +++ b/packages/db/src/data-source.ts @@ -0,0 +1,28 @@ +import "reflect-metadata"; +import { DataSource } from "typeorm"; +import dotenv from "dotenv"; + +// import your entities/tables here +import { TestTable } from "./entities/TestTable.entity"; + +dotenv.config({ path: ".db.env" }); + +export const AppDataSource = new DataSource({ + // database name + database: process.env.DB_NAME || "postgres", + // entity schemas, whenever you make a table you have to add it here + entities: [TestTable], + // database host (if it's localhost or not) + host: process.env.DB_HOST || "localhost", + // logging: https://typeorm.io/docs/advanced-topics/logging + // you can enable logging only in development to avoid performance issues + // there are also different level of logging you can set like "all", "query", etc. + logging: process.env.NODE_ENV === "development", + // the migrations directory (not sure if this works yet) + migrations: [__dirname + "/migrations/*.{js,ts}"], + password: process.env.POSTGRES_PASSWORD || "postgres", + port: parseInt(process.env.DB_PORT || "5432"), + synchronize: process.env.NODE_ENV === "development", // Only in development + type: "postgres", + username: process.env.POSTGRES_USER || "postgres", +}); diff --git a/packages/db/src/entities/TestTable.entity.ts b/packages/db/src/entities/TestTable.entity.ts new file mode 100644 index 00000000..9d90bf42 --- /dev/null +++ b/packages/db/src/entities/TestTable.entity.ts @@ -0,0 +1,45 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, +} from "typeorm"; +import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; + +@Entity("test_table") +@Hypertable({ + compression: { + compress: true, + compress_orderby: "timestamp DESC", + compress_segmentby: "name", + }, +}) +export class TestTable { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @TimeColumn() + timestamp!: Date; + + @Column({ type: "varchar", length: 255 }) + name!: string; + + @Column({ type: "text", nullable: true }) + description?: string; + + @Column({ type: "boolean", default: true }) + isActive!: boolean; + + @Column({ type: "int", default: 0 }) + value!: number; + + @Column({ type: "text" }) + rfid!: string; + + @CreateDateColumn() + createdAt!: Date; + + @UpdateDateColumn() + updatedAt!: Date; +} diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts new file mode 100644 index 00000000..5952f0ec --- /dev/null +++ b/packages/db/src/index.ts @@ -0,0 +1,22 @@ +export { AppDataSource } from "./data-source"; + +// entities +export { TestTable } from "./entities/TestTable.entity"; + +// repositories +export { BaseRepository } from "./repositories/BaseRepository"; +export { TestTableRepository } from "./repositories/TestTableRepository"; + +// interfaces +export * from "./interfaces/repositories.interface"; + +// services +export { DatabaseService } from "./services/DatabaseService"; +export { + TestTableService, + type CreateTestTableDto, + type UpdateTestTableDto, +} from "./services/TestTableService"; + +// types +export * from "./types"; diff --git a/packages/db/src/interfaces/repositories.interface.ts b/packages/db/src/interfaces/repositories.interface.ts new file mode 100644 index 00000000..ed5f508f --- /dev/null +++ b/packages/db/src/interfaces/repositories.interface.ts @@ -0,0 +1,17 @@ +import { DeepPartial, FindManyOptions, FindOneOptions } from "typeorm"; +import { TestTable } from "../entities/TestTable.entity"; + +export interface IBaseRepository { + create(entity: DeepPartial): Promise; + findById(id: string | number): Promise; + findOne(options: FindOneOptions): Promise; + findMany(options?: FindManyOptions): Promise; + update(id: string | number, updates: DeepPartial): Promise; + delete(id: string | number): Promise; + count(options?: FindManyOptions): Promise; +} + +export interface ITestTableRepository extends IBaseRepository { + findByName(name: string): Promise; + findActiveRecords(): Promise; +} diff --git a/packages/db/src/repositories/BaseRepository.ts b/packages/db/src/repositories/BaseRepository.ts new file mode 100644 index 00000000..8ee5aad5 --- /dev/null +++ b/packages/db/src/repositories/BaseRepository.ts @@ -0,0 +1,53 @@ +import { + Repository, + FindOptionsWhere, + FindManyOptions, + DeepPartial, +} from "typeorm"; +import { ObjectLiteral, FindOneOptions } from "typeorm"; + +// Define a base entity interface that has an id +interface BaseEntity { + id: number | string; +} + +export abstract class BaseRepository { + constructor(protected repository: Repository) {} + + async findAll(options?: FindManyOptions): Promise { + return this.repository.find(options); + } + + async findOne(options: FindOneOptions): Promise { + return this.repository.findOne(options); + } + + async findById(id: number | string): Promise { + return this.repository.findOne({ + where: { id } as FindOptionsWhere, + }); + } + + async create(data: DeepPartial): Promise { + const entity = this.repository.create(data); + return this.repository.save(entity); + } + + async update(id: number | string, data: DeepPartial): Promise { + await this.repository.update(id, data); + return this.findById(id); + } + + async delete(id: number | string): Promise { + const result = await this.repository.delete(id); + return (result.affected ?? 0) > 0; + } + + async save(entity: DeepPartial): Promise { + return this.repository.save(entity); + } + + async count(options?: FindManyOptions): Promise { + return this.repository.count(options); + } +} diff --git a/packages/db/src/repositories/TestTableRepository.ts b/packages/db/src/repositories/TestTableRepository.ts new file mode 100644 index 00000000..f05d6147 --- /dev/null +++ b/packages/db/src/repositories/TestTableRepository.ts @@ -0,0 +1,49 @@ +import { Repository, Like, FindManyOptions } from "typeorm"; +import { BaseRepository } from "./BaseRepository"; +import { TestTable } from "../entities/TestTable.entity"; +import { ITestTableRepository } from "../interfaces/repositories.interface"; + +export class TestTableRepository + extends BaseRepository + implements ITestTableRepository +{ + constructor(repository: Repository) { + super(repository); + } + + async findMany( + options?: FindManyOptions | undefined, + ): Promise { + return this.repository.find(options); + } + + async findByName(name: string): Promise { + return await this.repository.find({ + order: { + createdAt: "DESC", + }, + where: { + name: Like(`%${name}%`), + }, + }); + } + + async findActiveRecords(): Promise { + return await this.repository.find({ + order: { + updatedAt: "DESC", + }, + where: { + isActive: true, + }, + }); + } + + async findByValueRange(min: number, max: number): Promise { + return await this.repository + .createQueryBuilder("test") + .where("test.value >= :min AND test.value <= :max", { max, min }) + .orderBy("test.value", "ASC") + .getMany(); + } +} diff --git a/packages/db/src/services/DatabaseService.ts b/packages/db/src/services/DatabaseService.ts new file mode 100644 index 00000000..8b011f7b --- /dev/null +++ b/packages/db/src/services/DatabaseService.ts @@ -0,0 +1,50 @@ +import { DataSource } from 'typeorm'; +import { AppDataSource } from '../data-source'; +import { TestTable } from '../entities/TestTable.entity'; +import { TestTableRepository } from '../repositories/TestTableRepository'; + +export class DatabaseService { + private dataSource: DataSource; + private _testTableRepository: TestTableRepository | null = null; + + constructor() { + this.dataSource = AppDataSource; + } + + async initialize(): Promise { + try { + if (!this.dataSource.isInitialized) { + await this.dataSource.initialize(); + console.log('Database connection initialized successfully'); + } + } catch (error) { + console.error('Error during database initialization:', error); + throw error; + } + } + + async close(): Promise { + if (this.dataSource.isInitialized) { + await this.dataSource.destroy(); + console.log('Database connection closed'); + } + } + + get testTableRepository(): TestTableRepository { + if (!this._testTableRepository) { + const repository = this.dataSource.getRepository(TestTable); + this._testTableRepository = new TestTableRepository(repository); + } + return this._testTableRepository; + } + + // Helper method to check if database is connected + get isConnected(): boolean { + return this.dataSource.isInitialized; + } + + // Get the raw data source if needed for advanced operations + get rawDataSource(): DataSource { + return this.dataSource; + } +} diff --git a/packages/db/src/services/TestTableService.ts b/packages/db/src/services/TestTableService.ts new file mode 100644 index 00000000..0e182bd1 --- /dev/null +++ b/packages/db/src/services/TestTableService.ts @@ -0,0 +1,96 @@ +import { TestTable } from "../entities/TestTable.entity"; +import { TestTableRepository } from "../repositories/TestTableRepository"; + +export interface CreateTestTableDto { + name: string; + description?: string; + value?: number; + isActive?: boolean; +} + +export interface UpdateTestTableDto { + name?: string; + description?: string; + value?: number; + isActive?: boolean; +} + +export class TestTableService { + constructor(private testTableRepository: TestTableRepository) {} + + async createTestEntry(data: CreateTestTableDto): Promise { + // Add business logic here (validation, transformation, etc.) + if (!data.name || data.name.trim().length === 0) { + throw new Error("Name is required"); + } + + if (data.name.length > 255) { + throw new Error("Name must be less than 255 characters"); + } + + return await this.testTableRepository.create({ + description: data.description?.trim(), + isActive: data.isActive ?? true, + name: data.name.trim(), + value: data.value ?? 0, + }); + } + + async updateTestEntry( + id: string, + data: UpdateTestTableDto, + ): Promise { + // Business logic for updates + if (data.name !== undefined && data.name.trim().length === 0) { + throw new Error("Name cannot be empty"); + } + + if (data.name && data.name.length > 255) { + throw new Error("Name must be less than 255 characters"); + } + + const updateData: Partial = {}; + if (data.name !== undefined) updateData.name = data.name.trim(); + if (data.description !== undefined) + updateData.description = data.description?.trim(); + if (data.value !== undefined) updateData.value = data.value; + if (data.isActive !== undefined) updateData.isActive = data.isActive; + + return await this.testTableRepository.update(id, updateData); + } + + async getTestEntryById(id: string): Promise { + return await this.testTableRepository.findById(id); + } + + async getAllTestEntries(): Promise { + return await this.testTableRepository.findMany({ + order: { createdAt: "DESC" }, + }); + } + + async getActiveTestEntries(): Promise { + return await this.testTableRepository.findActiveRecords(); + } + + async searchTestEntriesByName(name: string): Promise { + if (!name || name.trim().length === 0) { + throw new Error("Search name cannot be empty"); + } + + return await this.testTableRepository.findByName(name.trim()); + } + + async deleteTestEntry(id: string): Promise { + const exists = await this.testTableRepository.findById(id); + if (!exists) { + throw new Error("Test entry not found"); + } + + return await this.testTableRepository.delete(id); + } + + async getTestEntriesCount(): Promise { + return await this.testTableRepository.count(); + } +} diff --git a/packages/db/src/types.ts b/packages/db/src/types.ts new file mode 100644 index 00000000..4a09e130 --- /dev/null +++ b/packages/db/src/types.ts @@ -0,0 +1,5 @@ +export interface PageViewStats { + interval: string; + count: number; + unique_users: number; +} diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json new file mode 100644 index 00000000..fda95bc7 --- /dev/null +++ b/packages/db/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["esnext"], + "module": "commonjs", + "moduleResolution": "node", + "outDir": "./dist", + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "strict": true + }, + "include": ["src", "migrations"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/server/package.json b/packages/server/package.json index 64087d06..48f4cc1e 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -4,9 +4,9 @@ "version": "0.1.0", "scripts": { "clean": "rimraf dist", - "start": "node -r ts-node/register -r tsconfig-paths/register dist/server.js", + "start": "node dist/server.js", "build": "npm run clean && npm run tsc", - "dev": "NODE_ENV=development nodemon - exec 'ts-node' src/server.ts", + "dev": "NODE_ENV=development nodemon --exec ts-node -r tsconfig-paths/register src/server.ts", "tsc": "tsc -p ./tsconfig.json" }, "dependencies": { @@ -20,6 +20,7 @@ "axios": "^1.7.2", "axios-retry": "^4.0.0", "cors": "^2.8.5", + "db": "*", "dotenv": "^16.4.5", "express": "^4.19.2", "log4js": "^6.9.1", @@ -33,6 +34,9 @@ "typescript": "^5.5.3" }, "devDependencies": { + "@timescaledb/core": "^0.0.1", + "@timescaledb/schemas": "^0.0.1", + "@timescaledb/typeorm": "^0.0.1", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/eslint": "^9.6.0", "@types/express": "^4.17.21", diff --git a/packages/server/src/controllers/BackendController/BackendController.ts b/packages/server/src/controllers/BackendController/BackendController.ts index 0947a390..9f4e4bb2 100644 --- a/packages/server/src/controllers/BackendController/BackendController.ts +++ b/packages/server/src/controllers/BackendController/BackendController.ts @@ -7,6 +7,7 @@ import DynamoDB from "@/datasources/DynamoDB/DynamoDB"; import { SocketIO } from "@/datasources/SocketIO/SocketIO"; import { SolarMQTTClient } from "@/datasources/SolarMQTTClient/SolarMQTTClient"; import { options } from "@/datasources/SolarMQTTClient/SolarMQTTClient.types"; +import { DatabaseManager } from "@/database/DatabaseManager"; import { logger } from "@/index"; import { type ITelemetryData } from "@shared/helios-types"; @@ -17,6 +18,7 @@ export class BackendController implements BackendControllerTypes { public socketIO: SocketIO; public lapController: LapController; public mqtt: SolarMQTTClient; + public databaseManager: DatabaseManager; public carLatency: number; constructor( httpsServer: Server, @@ -25,11 +27,25 @@ export class BackendController implements BackendControllerTypes { this.socketIO = new SocketIO(httpsServer, this); this.mqtt = new SolarMQTTClient(options, this); this.lapController = new LapController(this); + this.databaseManager = DatabaseManager.getInstance(); this.establishCarPinging(); this.carLatency = 0; + this.initializeDatabase(); // this.handleCarLatency(); } + private async initializeDatabase() { + try { + await this.databaseManager.initialize(); + logger.info("Database connection established successfully"); + } catch (error) { + logger.error("Failed to initialize database:", error); + // Optionally throw or handle gracefully based on your needs + // For non-critical features, you might continue without database + // throw error; + } + } + public establishCarPinging() { // Ping the car every 5 seconds this.mqtt.pingTimer(5000); @@ -64,4 +80,13 @@ export class BackendController implements BackendControllerTypes { this.socketIO.broadcastCarConnect({ message: "Car has connected" }); logger.info("Car connect event broadcasted to frontend"); } + + public async cleanup() { + try { + await this.databaseManager.close(); + logger.info("Database connection closed successfully"); + } catch (error) { + logger.error("Error closing database connection:", error); + } + } } diff --git a/packages/server/src/controllers/TestTableController.ts b/packages/server/src/controllers/TestTableController.ts new file mode 100644 index 00000000..8247a619 --- /dev/null +++ b/packages/server/src/controllers/TestTableController.ts @@ -0,0 +1,137 @@ +import { Request, Response } from 'express'; +import { DatabaseManager } from '../database/DatabaseManager'; +import { CreateTestTableDto, UpdateTestTableDto } from 'db'; + +export class TestTableController { + private databaseManager: DatabaseManager; + + constructor() { + this.databaseManager = DatabaseManager.getInstance(); + } + + // POST /api/test-table + public create = async (req: Request, res: Response): Promise => { + try { + const data: CreateTestTableDto = req.body; + const result = await this.databaseManager.testTableService.createTestEntry(data); + + res.status(201).json({ + success: true, + data: result, + }); + } catch (error) { + res.status(400).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }; + + // GET /api/test-table + public getAll = async (_req: Request, res: Response): Promise => { + try { + const results = await this.databaseManager.testTableService.getAllTestEntries(); + + res.json({ + success: true, + data: results, + count: results.length, + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }; + + // GET /api/test-table/:id + public getById = async (req: Request, res: Response): Promise => { + try { + const { id } = req.params; + const result = await this.databaseManager.testTableService.getTestEntryById(id); + + if (!result) { + res.status(404).json({ + success: false, + error: 'Test entry not found', + }); + return; + } + + res.json({ + success: true, + data: result, + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }; + + // PUT /api/test-table/:id + public update = async (req: Request, res: Response): Promise => { + try { + const { id } = req.params; + const data: UpdateTestTableDto = req.body; + const result = await this.databaseManager.testTableService.updateTestEntry(id, data); + + if (!result) { + res.status(404).json({ + success: false, + error: 'Test entry not found', + }); + return; + } + + res.json({ + success: true, + data: result, + }); + } catch (error) { + res.status(400).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }; + + // DELETE /api/test-table/:id + public delete = async (req: Request, res: Response): Promise => { + try { + const { id } = req.params; + const success = await this.databaseManager.testTableService.deleteTestEntry(id); + + res.json({ + success: success, + message: success ? 'Test entry deleted successfully' : 'Failed to delete test entry', + }); + } catch (error) { + res.status(400).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }; + + // GET /api/test-table/search/:name + public searchByName = async (req: Request, res: Response): Promise => { + try { + const { name } = req.params; + const results = await this.databaseManager.testTableService.searchTestEntriesByName(name); + + res.json({ + success: true, + data: results, + count: results.length, + }); + } catch (error) { + res.status(400).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }; +} diff --git a/packages/server/src/database/DatabaseManager.ts b/packages/server/src/database/DatabaseManager.ts new file mode 100644 index 00000000..be394358 --- /dev/null +++ b/packages/server/src/database/DatabaseManager.ts @@ -0,0 +1,53 @@ +import { DatabaseService, TestTableService } from 'db'; + +export class DatabaseManager { + private static instance: DatabaseManager; + private databaseService: DatabaseService; + private _testTableService: TestTableService | null = null; + + private constructor() { + this.databaseService = new DatabaseService(); + } + + public static getInstance(): DatabaseManager { + if (!DatabaseManager.instance) { + DatabaseManager.instance = new DatabaseManager(); + } + return DatabaseManager.instance; + } + + public async initialize(): Promise { + try { + await this.databaseService.initialize(); + console.log('DatabaseManager initialized successfully'); + } catch (error) { + console.error('Failed to initialize DatabaseManager:', error); + throw error; + } + } + + public async close(): Promise { + await this.databaseService.close(); + console.log('DatabaseManager closed'); + } + + // Lazy-load services + public get testTableService(): TestTableService { + if (!this._testTableService) { + this._testTableService = new TestTableService( + this.databaseService.testTableRepository + ); + } + return this._testTableService; + } + + // Helper method to check connection status + public get isConnected(): boolean { + return this.databaseService.isConnected; + } + + // Get raw database service if needed + public get rawDatabaseService(): DatabaseService { + return this.databaseService; + } +} diff --git a/packages/server/src/datasources/SolarMQTTClient/SolarMQTTClient.types.ts b/packages/server/src/datasources/SolarMQTTClient/SolarMQTTClient.types.ts index c8271dd9..14642d3d 100644 --- a/packages/server/src/datasources/SolarMQTTClient/SolarMQTTClient.types.ts +++ b/packages/server/src/datasources/SolarMQTTClient/SolarMQTTClient.types.ts @@ -1,5 +1,5 @@ import type { MqttClient } from "mqtt"; -import type { IClientOptions } from "mqtt/*"; +import type { IClientOptions } from "mqtt"; export const options: IClientOptions = { host: "localhost", diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 39bcc92e..a4376554 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -25,10 +25,13 @@ import { type TerminusOptions, createTerminus } from "@godaddy/terminus"; dotenv.config(); const app = express(); -let backendController: BackendController | null; +let backendController: BackendController | null = null; export const setBackendController = (backend: BackendController) => { backendController = backend; }; +export const getBackendController = (): BackendController | null => { + return backendController; +}; app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(cors()); @@ -62,6 +65,10 @@ const onSignal = async () => { logger.info("🚀 Server is starting cleanup"); try { + // Cleanup database connection + if (backendController) { + await backendController.cleanup(); + } logger.info("Kafka Consumer Disconnected"); } catch (err) { logger.error("Error disconnecting the kafka consumer", err as Error); diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 724da303..8b263a61 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -1,7 +1,7 @@ import { createLightweightApplicationLogger } from "@/utils/logger"; import { startAedes } from "@/aedes"; -import { server, setBackendController } from "@/index"; +import { server, setBackendController, getBackendController } from "@/index"; import main from "@/main"; const logger = createLightweightApplicationLogger("server.ts"); @@ -26,3 +26,31 @@ export const httpServer = server const backendController = main(httpServer); setBackendController(backendController); }); + +// Graceful shutdown handling +process.on('SIGTERM', async () => { + logger.info('Received SIGTERM, shutting down gracefully'); + await gracefulShutdown(); +}); + +process.on('SIGINT', async () => { + logger.info('Received SIGINT, shutting down gracefully'); + await gracefulShutdown(); +}); + +async function gracefulShutdown() { + try { + const backendController = getBackendController(); + if (backendController) { + await backendController.cleanup(); + } + + httpServer.close(() => { + logger.info('Server closed successfully'); + process.exit(0); + }); + } catch (error) { + logger.error('Error during graceful shutdown:', error); + process.exit(1); + } +} diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index d3a6ddc9..c31adf32 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -1,11 +1,8 @@ { - "ts-node": { - "require": ["tsconfig-paths/register"] - }, "compilerOptions": { - "module": "CommonJS", + "module": "commonjs", "esModuleInterop": true, - "target": "ES2020", + "target": "es2020", "noImplicitAny": false, "moduleResolution": "node", "sourceMap": true, @@ -15,6 +12,8 @@ "@/*": ["src/*"] }, "jsx": "react-jsx", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, "resolveJsonModule": true }, "include": [".eslintrc.cjs", "src/**/*", "**/*.cjs", "**/*.mjs"], diff --git a/test.ts b/test.ts new file mode 100644 index 00000000..b4b716ce --- /dev/null +++ b/test.ts @@ -0,0 +1,5 @@ +const test = () => { + (e) => { + console.log("Hello, world!"); + }; +}; diff --git a/yarn.lock b/yarn.lock index 4ef32b34..9aef7312 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6454,6 +6454,13 @@ __metadata: languageName: node linkType: hard +"@faker-js/faker@npm:^9.3.0": + version: 9.9.0 + resolution: "@faker-js/faker@npm:9.9.0" + checksum: 10c0/02107cb6915217b4831fa4b261603165972eb7f09d2ab2e1d4b75807df2d425a2bac8621cddbfdcc158c2c447c2bce2e14939d51c4a63fbd832df55452e7f73c + languageName: node + linkType: hard + "@fastify/busboy@npm:^3.1.1": version: 3.1.1 resolution: "@fastify/busboy@npm:3.1.1" @@ -10544,6 +10551,13 @@ __metadata: languageName: node linkType: hard +"@sqltools/formatter@npm:^1.2.5": + version: 1.2.5 + resolution: "@sqltools/formatter@npm:1.2.5" + checksum: 10c0/4b4fa62b8cd4880784b71cc5edd4a13da04fda0a915c14282765a8ec1a900a495e69b322704413e2052d221b5646d9fb0e20e87911f9a8f438f33180eecb11a4 + languageName: node + linkType: hard + "@swc/counter@npm:0.1.3": version: 0.1.3 resolution: "@swc/counter@npm:0.1.3" @@ -10579,6 +10593,48 @@ __metadata: languageName: node linkType: hard +"@timescaledb/core@npm:0.0.1, @timescaledb/core@npm:^0.0.1": + version: 0.0.1 + resolution: "@timescaledb/core@npm:0.0.1" + dependencies: + "@timescaledb/schemas": "npm:0.0.1" + "@timescaledb/utils": "npm:0.0.1" + checksum: 10c0/f51dab03cdfd424560daad4634e40064b01daf9e637000d20eac44b3a14bd6306a4bdc56682b4b059d63c31a63673e1d721f2c23f74ff3112d3455cb74be2f20 + languageName: node + linkType: hard + +"@timescaledb/schemas@npm:0.0.1, @timescaledb/schemas@npm:^0.0.1": + version: 0.0.1 + resolution: "@timescaledb/schemas@npm:0.0.1" + dependencies: + zod: "npm:^3.24.1" + checksum: 10c0/3f1b93584e9797b9e9a68172bc316e5ffa3220636a2a98f5f488c3e6fc7b99ff88a19ae208b1e9983bb8c4188e825a50ac0b44555baaa552601d734ca8e1b37f + languageName: node + linkType: hard + +"@timescaledb/typeorm@npm:^0.0.1": + version: 0.0.1 + resolution: "@timescaledb/typeorm@npm:0.0.1" + dependencies: + "@timescaledb/core": "npm:0.0.1" + "@timescaledb/schemas": "npm:0.0.1" + "@timescaledb/utils": "npm:0.0.1" + reflect-metadata: "npm:^0.2.2" + typeorm: "npm:^0.3.20" + checksum: 10c0/4c65458cee1652e590c97a840ff1a0847057e8f2fa2f82e32a948d2ffbd23e45400b5071ecb0b4a54a9f3239cfc945a0e4c54291e2cb2e932be61eb42d9aab06 + languageName: node + linkType: hard + +"@timescaledb/utils@npm:0.0.1": + version: 0.0.1 + resolution: "@timescaledb/utils@npm:0.0.1" + dependencies: + "@timescaledb/schemas": "npm:0.0.1" + debug: "npm:^4.4.0" + checksum: 10c0/8c44967d0d97627dfeb4b9725e3b26a357aa88a7e0ed7665d87b07456f5403ce397f9dc07e23221b786e3a14b060b99108795764af0af59efaf5aaf387807914 + languageName: node + linkType: hard + "@tootallnate/once@npm:1": version: 1.1.2 resolution: "@tootallnate/once@npm:1.1.2" @@ -10928,6 +10984,17 @@ __metadata: languageName: node linkType: hard +"@types/pg@npm:^8.11.0": + version: 8.15.5 + resolution: "@types/pg@npm:8.15.5" + dependencies: + "@types/node": "npm:*" + pg-protocol: "npm:*" + pg-types: "npm:^2.2.0" + checksum: 10c0/19a3cc1811918753f8c827733648c3a85c7b0355bf207c44eb1a3b79b2e6a0d85cb5457ec550d860fc9be7e88c7587a3600958ec8c61fa1ad573061c63af93f0 + languageName: node + linkType: hard + "@types/plotly.js-dist-min@npm:^2": version: 2.3.4 resolution: "@types/plotly.js-dist-min@npm:2.3.4" @@ -11970,6 +12037,13 @@ __metadata: languageName: node linkType: hard +"ansis@npm:^3.17.0": + version: 3.17.0 + resolution: "ansis@npm:3.17.0" + checksum: 10c0/d8fa94ca7bb91e7e5f8a7d323756aa075facce07c5d02ca883673e128b2873d16f93e0dec782f98f1eeb1f2b3b4b7b60dcf0ad98fb442e75054fe857988cc5cb + languageName: node + linkType: hard + "any-promise@npm:^1.0.0": version: 1.3.0 resolution: "any-promise@npm:1.3.0" @@ -11987,6 +12061,13 @@ __metadata: languageName: node linkType: hard +"app-root-path@npm:^3.1.0": + version: 3.1.0 + resolution: "app-root-path@npm:3.1.0" + checksum: 10c0/4a0fd976de1bffcdb18a5e1f8050091f15d0780e0582bca99aaa9d52de71f0e08e5185355fcffc781180bfb898499e787a2f5ed79b9c448b942b31dc947acaa9 + languageName: node + linkType: hard + "aproba@npm:2.0.0, aproba@npm:^1.0.3 || ^2.0.0": version: 2.0.0 resolution: "aproba@npm:2.0.0" @@ -14095,6 +14176,24 @@ __metadata: languageName: node linkType: hard +"db@npm:*, db@workspace:packages/db": + version: 0.0.0-use.local + resolution: "db@workspace:packages/db" + dependencies: + "@faker-js/faker": "npm:^9.3.0" + "@timescaledb/core": "npm:^0.0.1" + "@timescaledb/schemas": "npm:^0.0.1" + "@timescaledb/typeorm": "npm:^0.0.1" + "@types/pg": "npm:^8.11.0" + dotenv: "npm:^16.4.5" + express: "npm:^4.18.2" + pg: "npm:^8.11.3" + reflect-metadata: "npm:^0.1.13" + typeorm: "npm:^0.3.20" + typescript: "npm:^5.5.3" + languageName: unknown + linkType: soft + "debounce-promise@npm:^3.1.2": version: 3.1.2 resolution: "debounce-promise@npm:3.1.2" @@ -14208,6 +14307,18 @@ __metadata: languageName: node linkType: hard +"dedent@npm:^1.6.0": + version: 1.7.0 + resolution: "dedent@npm:1.7.0" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10c0/c5e8a8beb5072bd5e520cb64b27a82d7ec3c2a63ee5ce47dbc2a05d5b7700cefd77a992a752cd0a8b1d979c1db06b14fb9486e805f3ad6088eda6e07cd9bf2d5 + languageName: node + linkType: hard + "deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -14516,6 +14627,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.4.7": + version: 16.6.1 + resolution: "dotenv@npm:16.6.1" + checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc + languageName: node + linkType: hard + "dotenv@npm:~16.4.5": version: 16.4.7 resolution: "dotenv@npm:16.4.7" @@ -15666,7 +15784,7 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.19.2": +"express@npm:^4.18.2, express@npm:^4.19.2": version: 4.21.2 resolution: "express@npm:4.21.2" dependencies: @@ -16583,7 +16701,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.2.7, glob@npm:^10.3.10, glob@npm:^10.3.7": +"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.2.7, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.5": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -20947,6 +21065,87 @@ __metadata: languageName: node linkType: hard +"pg-cloudflare@npm:^1.2.7": + version: 1.2.7 + resolution: "pg-cloudflare@npm:1.2.7" + checksum: 10c0/8a52713dbdecc9d389dc4e65e3b7ede2e199ec3715f7491ee80a15db171f2d75677a102e9c2cef0cb91a2f310e91f976eaec0dd6ef5d8bf357de0b948f9d9431 + languageName: node + linkType: hard + +"pg-connection-string@npm:^2.9.1": + version: 2.9.1 + resolution: "pg-connection-string@npm:2.9.1" + checksum: 10c0/9a646529bbc0843806fc5de98ce93735a4612b571f11867178a85665d11989a827e6fd157388ca0e34ec948098564fce836c178cfd499b9f0e8cd9972b8e2e5c + languageName: node + linkType: hard + +"pg-int8@npm:1.0.1": + version: 1.0.1 + resolution: "pg-int8@npm:1.0.1" + checksum: 10c0/be6a02d851fc2a4ae3e9de81710d861de3ba35ac927268973eb3cb618873a05b9424656df464dd43bd7dc3fc5295c3f5b3c8349494f87c7af50ec59ef14e0b98 + languageName: node + linkType: hard + +"pg-pool@npm:^3.10.1": + version: 3.10.1 + resolution: "pg-pool@npm:3.10.1" + peerDependencies: + pg: ">=8.0" + checksum: 10c0/a00916b7df64226cc597fe769e3a757ff9b11562dc87ce5b0a54101a18c1fe282daaa2accaf27221e81e1e4cdf4da6a33dab09614734d32904d6c4e11c44a079 + languageName: node + linkType: hard + +"pg-protocol@npm:*, pg-protocol@npm:^1.10.3": + version: 1.10.3 + resolution: "pg-protocol@npm:1.10.3" + checksum: 10c0/f7ef54708c93ee6d271e37678296fc5097e4337fca91a88a3d99359b78633dbdbf6e983f0adb34b7cdd261b7ec7266deb20c3233bf3dfdb498b3e1098e8750b9 + languageName: node + linkType: hard + +"pg-types@npm:2.2.0, pg-types@npm:^2.2.0": + version: 2.2.0 + resolution: "pg-types@npm:2.2.0" + dependencies: + pg-int8: "npm:1.0.1" + postgres-array: "npm:~2.0.0" + postgres-bytea: "npm:~1.0.0" + postgres-date: "npm:~1.0.4" + postgres-interval: "npm:^1.1.0" + checksum: 10c0/ab3f8069a323f601cd2d2279ca8c425447dab3f9b61d933b0601d7ffc00d6200df25e26a4290b2b0783b59278198f7dd2ed03e94c4875797919605116a577c65 + languageName: node + linkType: hard + +"pg@npm:^8.11.3": + version: 8.16.3 + resolution: "pg@npm:8.16.3" + dependencies: + pg-cloudflare: "npm:^1.2.7" + pg-connection-string: "npm:^2.9.1" + pg-pool: "npm:^3.10.1" + pg-protocol: "npm:^1.10.3" + pg-types: "npm:2.2.0" + pgpass: "npm:1.0.5" + peerDependencies: + pg-native: ">=3.0.1" + dependenciesMeta: + pg-cloudflare: + optional: true + peerDependenciesMeta: + pg-native: + optional: true + checksum: 10c0/a6a407ff0efb7599760d72ffdcda47a74c34c0fd71d896623caac45cf2cfb0f49a10973cce23110f182b9810639a1e9f6904454d7358c7001574ee0ffdcbce2a + languageName: node + linkType: hard + +"pgpass@npm:1.0.5": + version: 1.0.5 + resolution: "pgpass@npm:1.0.5" + dependencies: + split2: "npm:^4.1.0" + checksum: 10c0/5ea6c9b2de04c33abb08d33a2dded303c4a3c7162a9264519cbe85c0a9857d712463140ba42fad0c7cd4b21f644dd870b45bb2e02fcbe505b4de0744fd802c1d + languageName: node + linkType: hard + "picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" @@ -21169,6 +21368,36 @@ __metadata: languageName: node linkType: hard +"postgres-array@npm:~2.0.0": + version: 2.0.0 + resolution: "postgres-array@npm:2.0.0" + checksum: 10c0/cbd56207e4141d7fbf08c86f2aebf21fa7064943d3f808ec85f442ff94b48d891e7a144cc02665fb2de5dbcb9b8e3183a2ac749959e794b4a4cfd379d7a21d08 + languageName: node + linkType: hard + +"postgres-bytea@npm:~1.0.0": + version: 1.0.0 + resolution: "postgres-bytea@npm:1.0.0" + checksum: 10c0/febf2364b8a8953695cac159eeb94542ead5886792a9627b97e33f6b5bb6e263bc0706ab47ec221516e79fbd6b2452d668841830fb3b49ec6c0fc29be61892ce + languageName: node + linkType: hard + +"postgres-date@npm:~1.0.4": + version: 1.0.7 + resolution: "postgres-date@npm:1.0.7" + checksum: 10c0/0ff91fccc64003e10b767fcfeefb5eaffbc522c93aa65d5051c49b3c4ce6cb93ab091a7d22877a90ad60b8874202c6f1d0f935f38a7235ed3b258efd54b97ca9 + languageName: node + linkType: hard + +"postgres-interval@npm:^1.1.0": + version: 1.2.0 + resolution: "postgres-interval@npm:1.2.0" + dependencies: + xtend: "npm:^4.0.0" + checksum: 10c0/c1734c3cb79e7f22579af0b268a463b1fa1d084e742a02a7a290c4f041e349456f3bee3b4ee0bb3f226828597f7b76deb615c1b857db9a742c45520100456272 + languageName: node + linkType: hard + "postprocessing@npm:^6.32.1": version: 6.37.3 resolution: "postprocessing@npm:6.37.3" @@ -22081,6 +22310,13 @@ __metadata: languageName: node linkType: hard +"reflect-metadata@npm:^0.1.13": + version: 0.1.14 + resolution: "reflect-metadata@npm:0.1.14" + checksum: 10c0/3a6190c7f6cb224f26a012d11f9e329360c01c1945e2cbefea23976a8bacf9db6b794aeb5bf18adcb673c448a234fbc06fc41853c00a6c206b30f0777ecf019e + languageName: node + linkType: hard + "reflect-metadata@npm:^0.2.2": version: 0.2.2 resolution: "reflect-metadata@npm:0.2.2" @@ -22444,7 +22680,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 @@ -22601,6 +22837,9 @@ __metadata: "@faker-js/faker": "npm:^9.0.3" "@godaddy/terminus": "npm:^4.12.1" "@shared/helios-types": "npm:*" + "@timescaledb/core": "npm:^0.0.1" + "@timescaledb/schemas": "npm:^0.0.1" + "@timescaledb/typeorm": "npm:^0.0.1" "@trivago/prettier-plugin-sort-imports": "npm:^4.3.0" "@types/eslint": "npm:^9.6.0" "@types/express": "npm:^4.17.21" @@ -22613,6 +22852,7 @@ __metadata: axios: "npm:^1.7.2" axios-retry: "npm:^4.0.0" cors: "npm:^2.8.5" + db: "npm:*" dotenv: "npm:^16.4.5" eslint: "npm:8.57.0" eslint-config-prettier: "npm:^9.1.0" @@ -22708,6 +22948,19 @@ __metadata: languageName: node linkType: hard +"sha.js@npm:^2.4.12": + version: 2.4.12 + resolution: "sha.js@npm:2.4.12" + dependencies: + inherits: "npm:^2.0.4" + safe-buffer: "npm:^5.2.1" + to-buffer: "npm:^1.2.0" + bin: + sha.js: bin.js + checksum: 10c0/9d36bdd76202c8116abbe152a00055ccd8a0099cb28fc17c01fa7bb2c8cffb9ca60e2ab0fe5f274ed6c45dc2633d8c39cf7ab050306c231904512ba9da4d8ab1 + languageName: node + linkType: hard + "shallow-clone@npm:^3.0.0": version: 3.0.1 resolution: "shallow-clone@npm:3.0.1" @@ -23172,7 +23425,7 @@ __metadata: languageName: node linkType: hard -"split2@npm:^4.2.0": +"split2@npm:^4.1.0, split2@npm:^4.2.0": version: 4.2.0 resolution: "split2@npm:4.2.0" checksum: 10c0/b292beb8ce9215f8c642bb68be6249c5a4c7f332fc8ecadae7be5cbdf1ea95addc95f0459ef2e7ad9d45fd1064698a097e4eb211c83e772b49bc0ee423e91534 @@ -23211,6 +23464,13 @@ __metadata: languageName: node linkType: hard +"sql-highlight@npm:^6.0.0": + version: 6.1.0 + resolution: "sql-highlight@npm:6.1.0" + checksum: 10c0/9614f4608bfde8ea7bf9b2fe9233dcc99a619c91cbc3f5cd85a6fb5ad4b2177f4ac8ca4a0191f4243ff8aea3b6f2a1229efc88635298269e0049b2ac08bde263 + languageName: node + linkType: hard + "sqlite3@npm:^5.1.7": version: 5.1.7 resolution: "sqlite3@npm:5.1.7" @@ -23985,6 +24245,17 @@ __metadata: languageName: node linkType: hard +"to-buffer@npm:^1.2.0": + version: 1.2.2 + resolution: "to-buffer@npm:1.2.2" + dependencies: + isarray: "npm:^2.0.5" + safe-buffer: "npm:^5.2.1" + typed-array-buffer: "npm:^1.0.3" + checksum: 10c0/56bc56352f14a2c4a0ab6277c5fc19b51e9534882b98eb068b39e14146591e62fa5b06bf70f7fed1626230463d7e60dca81e815096656e5e01c195c593873d12 + languageName: node + linkType: hard + "to-fast-properties@npm:^1.0.3": version: 1.0.3 resolution: "to-fast-properties@npm:1.0.3" @@ -24458,6 +24729,83 @@ __metadata: languageName: node linkType: hard +"typeorm@npm:^0.3.20": + version: 0.3.27 + resolution: "typeorm@npm:0.3.27" + dependencies: + "@sqltools/formatter": "npm:^1.2.5" + ansis: "npm:^3.17.0" + app-root-path: "npm:^3.1.0" + buffer: "npm:^6.0.3" + dayjs: "npm:^1.11.13" + debug: "npm:^4.4.0" + dedent: "npm:^1.6.0" + dotenv: "npm:^16.4.7" + glob: "npm:^10.4.5" + sha.js: "npm:^2.4.12" + sql-highlight: "npm:^6.0.0" + tslib: "npm:^2.8.1" + uuid: "npm:^11.1.0" + yargs: "npm:^17.7.2" + peerDependencies: + "@google-cloud/spanner": ^5.18.0 || ^6.0.0 || ^7.0.0 + "@sap/hana-client": ^2.14.22 + better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 + ioredis: ^5.0.4 + mongodb: ^5.8.0 || ^6.0.0 + mssql: ^9.1.1 || ^10.0.1 || ^11.0.1 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 || ^5.0.14 + reflect-metadata: ^0.1.14 || ^0.2.0 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0 + peerDependenciesMeta: + "@google-cloud/spanner": + optional: true + "@sap/hana-client": + optional: true + better-sqlite3: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + bin: + typeorm: cli.js + typeorm-ts-node-commonjs: cli-ts-node-commonjs.js + typeorm-ts-node-esm: cli-ts-node-esm.js + checksum: 10c0/e0136e1d277496de1d1b327912d55af4855c83d9147896547d6da78ed485c6fc5a84a8469938afe006860c237415028391b47717743e6d4a7b60a52bc6d349aa + languageName: node + linkType: hard + "typescript@npm:<=4.5.0": version: 4.4.4 resolution: "typescript@npm:4.4.4" @@ -25404,7 +25752,7 @@ __metadata: languageName: node linkType: hard -"xtend@npm:^4.0.2, xtend@npm:~4.0.1": +"xtend@npm:^4.0.0, xtend@npm:^4.0.2, xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e @@ -25596,6 +25944,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.24.1": + version: 3.25.76 + resolution: "zod@npm:3.25.76" + checksum: 10c0/5718ec35e3c40b600316c5b4c5e4976f7fee68151bc8f8d90ec18a469be9571f072e1bbaace10f1e85cf8892ea12d90821b200e980ab46916a6166a4260a983c + languageName: node + linkType: hard + "zustand@npm:^4.1.2, zustand@npm:^4.3.2": version: 4.5.7 resolution: "zustand@npm:4.5.7"