@cheetah.js/orm
Version:
A simple ORM for Cheetah.js
288 lines (287 loc) • 9.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withDatabase = withDatabase;
const globby_1 = __importDefault(require("globby"));
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const core_1 = require("@cheetah.js/core");
const entities_1 = require("../domain/entities");
const orm_1 = require("../orm");
const orm_service_1 = require("../orm.service");
const bun_pg_driver_1 = require("../driver/bun-pg.driver");
const DEFAULT_SCHEMA = 'public';
const DEFAULT_CONNECTION = {
host: 'localhost',
port: 5432,
database: 'postgres',
username: 'postgres',
password: 'postgres',
driver: bun_pg_driver_1.BunPgDriver,
};
const sessionCache = new Map();
function getCacheKey(options) {
const connection = resolveConnection(options.connection);
return JSON.stringify({
host: connection.host,
port: connection.port,
database: connection.database,
schema: options.schema ?? DEFAULT_SCHEMA,
entityFile: options.entityFile,
migrationPath: connection.migrationPath,
});
}
async function withDatabase(arg1, arg2, arg3) {
const { routine: targetRoutine, options: targetOptions, statements } = await normalizeArgs(arg1, arg2, arg3);
const cacheKey = getCacheKey(targetOptions);
let cachedSession = sessionCache.get(cacheKey);
const schemaStatements = await resolveSchemaStatements(statements, targetOptions);
if (!cachedSession) {
const session = await createSession(targetOptions);
cachedSession = {
orm: session.orm,
tables: [],
schema: session.schema,
};
sessionCache.set(cacheKey, cachedSession);
}
const context = buildContext(cachedSession.orm);
await dropAndRecreateSchema(context, cachedSession.schema);
await prepareSchema(context, cachedSession.schema);
await createTables(context, schemaStatements);
await targetRoutine(context);
}
async function createSession(options) {
const logger = selectLogger(options);
const orm = new orm_1.Orm(logger);
await initializeOrm(orm, options);
return { orm, schema: options.schema ?? DEFAULT_SCHEMA };
}
function selectLogger(options) {
if (options.logger) {
return options.logger;
}
const config = { applicationConfig: { logger: { level: 'info' } } };
return new core_1.LoggerService(config);
}
async function initializeOrm(orm, options) {
const storage = new entities_1.EntityStorage();
if (options.entityFile) {
const entityFiles = await (0, globby_1.default)(options.entityFile, { absolute: true });
for (const file of entityFiles) {
await Promise.resolve(`${file}`).then(s => __importStar(require(s)));
}
}
const service = new orm_service_1.OrmService(orm, storage, options.entityFile);
const connection = resolveConnection(options.connection);
await service.onInit(connection);
}
function resolveConnection(overrides) {
if (!overrides) {
return DEFAULT_CONNECTION;
}
return {
...DEFAULT_CONNECTION,
...overrides,
driver: overrides.driver ?? bun_pg_driver_1.BunPgDriver,
};
}
function buildContext(orm) {
return {
orm,
executeSql: (sql) => executeSql(orm, sql),
};
}
async function executeSql(orm, sql) {
if (!orm.driverInstance) {
throw new Error('Database driver not initialized. Call withDatabase() before executing SQL.');
}
const result = await orm.driverInstance.executeSql(sql);
return { rows: Array.isArray(result) ? result : [] };
}
async function createTables(context, statements) {
const payload = statements.filter(Boolean);
if (payload.length < 1) {
return;
}
await executeStatements(context, payload);
}
async function executeStatements(context, statements) {
for (const statement of statements) {
await context.executeSql(statement);
}
}
async function dropAndRecreateSchema(context, schema) {
await context.executeSql(`DROP SCHEMA IF EXISTS ${schema} CASCADE; CREATE SCHEMA ${schema};`);
}
async function normalizeArgs(tablesOrRoutine, routineOrOptions, optionsOrStatements) {
if (Array.isArray(tablesOrRoutine)) {
return {
routine: routineOrOptions,
options: optionsOrStatements ?? {},
statements: tablesOrRoutine,
};
}
return {
routine: tablesOrRoutine,
options: routineOrOptions ?? {},
statements: Array.isArray(optionsOrStatements) ? optionsOrStatements : [],
};
}
async function resolveSchemaStatements(statements, options) {
const explicit = statements.filter(Boolean);
if (explicit.length > 0) {
return explicit;
}
const fromMigrations = await loadStatementsFromMigrations(options);
return fromMigrations.filter(Boolean);
}
function normalizeGlobPatterns(patterns) {
return patterns.map(normalizeGlobPattern);
}
function normalizeGlobPattern(pattern) {
return pattern.replace(/\\/g, '/');
}
async function loadStatementsFromMigrations(options) {
const connection = resolveConnection(options.connection);
const patterns = await resolveMigrationPatterns(connection);
if (patterns.length < 1) {
return [];
}
const normalizedPatterns = normalizeGlobPatterns(patterns);
const files = await (0, globby_1.default)(normalizedPatterns, { absolute: true, expandDirectories: false });
if (files.length < 1) {
return [];
}
return extractStatementsFromFiles(files);
}
async function resolveMigrationPatterns(connection) {
if (connection.migrationPath) {
return [connection.migrationPath];
}
const inferred = await inferMigrationPathFromConfig();
if (inferred) {
return [inferred];
}
return [];
}
async function inferMigrationPathFromConfig() {
const configFile = await findConfigFile();
if (!configFile) {
return undefined;
}
const contents = await safeReadFile(configFile);
if (!contents) {
return undefined;
}
return extractMigrationPath(contents, configFile);
}
async function findConfigFile() {
const candidates = [
'cheetah.config.ts',
'cheetah.config.js',
'cheetah.config.mjs',
'cheetah.config.cjs',
];
for (const file of candidates) {
const fullPath = path_1.default.resolve(process.cwd(), file);
const exists = await fileExists(fullPath);
if (exists) {
return fullPath;
}
}
return undefined;
}
async function fileExists(target) {
try {
await fs_1.promises.access(target);
return true;
}
catch {
return false;
}
}
async function safeReadFile(file) {
try {
return await fs_1.promises.readFile(file, 'utf8');
}
catch {
return undefined;
}
}
function extractMigrationPath(source, file) {
const match = source.match(/migrationPath\s*:\s*['"`]([^'"`]+)['"`]/);
if (!match) {
return undefined;
}
const candidate = match[1].trim();
if (path_1.default.isAbsolute(candidate)) {
return candidate;
}
const baseDir = path_1.default.dirname(file);
return path_1.default.resolve(baseDir, candidate);
}
async function extractStatementsFromFiles(files) {
const statements = [];
for (const file of files) {
const payload = await safeReadFile(file);
if (!payload) {
continue;
}
const extracted = extractStatements(payload);
statements.push(...extracted);
}
return Array.from(new Set(statements));
}
function extractStatements(payload) {
const matches = payload.match(/(CREATE\s+(?:TABLE|TYPE|INDEX|SCHEMA)[\s\S]*?;|ALTER\s+TABLE[\s\S]*?\bADD\b[\s\S]*?;)/gi) ?? [];
return matches.map((statement) => statement.replace(/\s+/g, ' ').trim());
}
async function prepareSchema(context, schema) {
await context.executeSql(buildCreateSchemaStatement(schema));
await ensureSearchPath(context, schema);
}
function buildCreateSchemaStatement(schema) {
return `CREATE SCHEMA IF NOT EXISTS ${schema};`;
}
async function ensureSearchPath(context, schema) {
await context.executeSql(buildSearchPathStatement(schema));
}
function buildSearchPathStatement(schema) {
return `SET search_path TO ${schema};`;
}