#!/usr/bin/env node
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqlLogger = void 0;
const core_1 = require("@nestjs/core");
const schedule_1 = require("@nestjs/schedule");
const testing_1 = require("@nestjs/testing");
const nestjs_cls_1 = require("nestjs-cls");
const nestjs_kysely_1 = require("nestjs-kysely");
const nestjs_otel_1 = require("nestjs-otel");
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const sql_formatter_1 = require("sql-formatter");
const decorators_1 = require("../decorators");
const repositories_1 = require("../repositories");
const access_repository_1 = require("../repositories/access.repository");
const config_repository_1 = require("../repositories/config.repository");
const logging_repository_1 = require("../repositories/logging.repository");
const machine_learning_repository_1 = require("../repositories/machine-learning.repository");
const sync_repository_1 = require("../repositories/sync.repository");
const auth_service_1 = require("../services/auth.service");
const database_1 = require("../utils/database");
const handleError = (label, error) => {
    console.error(`${label} error: ${error}`);
};
class SqlLogger {
    queries = [];
    errors = [];
    clear() {
        this.queries = [];
        this.errors = [];
    }
    logQuery(query) {
        this.queries.push((0, sql_formatter_1.format)(query, { language: 'postgresql' }));
    }
    logQueryError(error, query) {
        this.errors.push({ error, query });
    }
}
exports.SqlLogger = SqlLogger;
const reflector = new core_1.Reflector();
class SqlGenerator {
    options;
    app = null;
    sqlLogger = new SqlLogger();
    results = {};
    constructor(options) {
        this.options = options;
    }
    async run() {
        try {
            await this.setup();
            for (const Repository of repositories_1.repositories) {
                if (Repository === logging_repository_1.LoggingRepository || Repository === machine_learning_repository_1.MachineLearningRepository) {
                    continue;
                }
                await this.process(Repository);
            }
            await this.write();
            this.stats();
        }
        finally {
            await this.close();
        }
    }
    async setup() {
        await (0, promises_1.rm)(this.options.targetDir, { force: true, recursive: true });
        await (0, promises_1.mkdir)(this.options.targetDir);
        if (!process.env.DB_HOSTNAME) {
            process.env.DB_HOSTNAME = 'localhost';
        }
        const { database, cls, otel } = new config_repository_1.ConfigRepository().getEnv();
        const moduleFixture = await testing_1.Test.createTestingModule({
            imports: [
                nestjs_kysely_1.KyselyModule.forRoot({
                    ...(0, database_1.getKyselyConfig)(database.config),
                    log: (event) => {
                        if (event.level === 'query') {
                            this.sqlLogger.logQuery(event.query.sql);
                        }
                        else if (event.level === 'error') {
                            this.sqlLogger.logQueryError(event.error, event.query.sql);
                            this.sqlLogger.logQuery(event.query.sql);
                        }
                    },
                }),
                nestjs_cls_1.ClsModule.forRoot(cls.config),
                nestjs_otel_1.OpenTelemetryModule.forRoot(otel),
            ],
            providers: [...repositories_1.repositories, auth_service_1.AuthService, schedule_1.SchedulerRegistry],
        }).compile();
        this.app = await moduleFixture.createNestApplication().init();
    }
    async process(Repository) {
        if (!this.app) {
            throw new Error('Not initialized');
        }
        const data = [`-- NOTE: This file is auto generated by ./sql-generator`];
        const instance = this.app.get(Repository);
        data.push(...(await this.runTargets(instance, `${Repository.name}`)));
        if (Repository.name === access_repository_1.AccessRepository.name || Repository.name === sync_repository_1.SyncRepository.name) {
            for (const key of Object.keys(instance)) {
                const subInstance = instance[key];
                data.push(...(await this.runTargets(subInstance, `${Repository.name}.${key}`)));
            }
        }
        this.results[Repository.name] = data;
    }
    async runTargets(instance, label) {
        const data = [];
        for (const key of this.getPropertyNames(instance)) {
            const target = instance[key];
            if (!(typeof target === 'function')) {
                continue;
            }
            const queries = reflector.get(decorators_1.GENERATE_SQL_KEY, target);
            if (!queries) {
                continue;
            }
            if (queries.length === 0) {
                queries.push({ params: [] });
            }
            for (const { name, params, stream } of queries) {
                let queryLabel = `${label}.${key}`;
                if (name) {
                    queryLabel += ` (${name})`;
                }
                this.sqlLogger.clear();
                if (stream) {
                    try {
                        const result = target.apply(instance, params);
                        for await (const _ of result) {
                            break;
                        }
                    }
                    catch (error) {
                        handleError(queryLabel, error);
                    }
                }
                else {
                    await target.apply(instance, params).catch((error) => handleError(queryLabel, error));
                }
                if (this.sqlLogger.queries.length === 0) {
                    console.warn(`No queries recorded for ${queryLabel}`);
                    continue;
                }
                data.push([`-- ${queryLabel}`, ...this.sqlLogger.queries].join('\n'));
            }
        }
        return data;
    }
    async write() {
        for (const [repoName, data] of Object.entries(this.results)) {
            if (data.length === 1) {
                continue;
            }
            const filename = repoName.replaceAll(/[A-Z]/g, (letter) => `.${letter.toLowerCase()}`).replace('.', '');
            const file = (0, node_path_1.join)(this.options.targetDir, `${filename}.sql`);
            await (0, promises_1.writeFile)(file, data.join('\n\n') + '\n');
        }
    }
    stats() {
        console.log(`Wrote ${Object.keys(this.results).length} files`);
        console.log(`Generated ${Object.values(this.results).flat().length} queries`);
    }
    async close() {
        if (this.app) {
            await this.app.close();
        }
    }
    getPropertyNames(instance) {
        return Object.getOwnPropertyNames(Object.getPrototypeOf(instance));
    }
}
new SqlGenerator({ targetDir: './src/queries' })
    .run()
    .then(() => {
    console.log('Done');
    process.exit(0);
})
    .catch((error) => {
    console.error(error);
    console.log('Something went wrong');
    process.exit(1);
});
//# sourceMappingURL=sync-sql.js.map