diff --git a/src/postgres/sql-loader.ts b/src/postgres/sql-loader.ts index 7bf8e14cc45..b2986ec64be 100644 --- a/src/postgres/sql-loader.ts +++ b/src/postgres/sql-loader.ts @@ -1,5 +1,26 @@ import { readFileSync } from 'fs'; -import { join } from 'path'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +function getDirname(): string { + if (typeof __dirname !== 'undefined' && __dirname) { + return __dirname; + } + const stack = new Error().stack || ''; + for (const line of stack.split('\n')) { + const match = line.match(/(file:\/\/\/[^\s)]+)/); + if (match) { + try { + return dirname(fileURLToPath(match[1])); + } catch { + // continue searching + } + } + } + throw new Error('Could not determine sql-loader directory path'); +} + +const currentDir = getDirname(); /** * Loads a migration's SQL from its `.sql` file — the portable source of truth @@ -10,7 +31,7 @@ import { join } from 'path'; * analogous to how the Lua scripts are bundled), so the same relative lookup * works at runtime. */ -const MIGRATIONS_DIR = join(__dirname, 'migrations'); +const MIGRATIONS_DIR = join(currentDir, 'migrations'); /** * Runtime queries live under `commands/`. Each `.sql` file is one parameterized @@ -20,7 +41,7 @@ const MIGRATIONS_DIR = join(__dirname, 'migrations'); * Python/Elixir/PHP/Rust ports (mirroring how the Redis backend's `.lua` * scripts never hardcode the key prefix). */ -const COMMANDS_DIR = join(__dirname, 'commands'); +const COMMANDS_DIR = join(currentDir, 'commands'); const migrationCache = new Map(); const commandCache = new Map(); diff --git a/tests/postgres/sql_loader.test.ts b/tests/postgres/sql_loader.test.ts new file mode 100644 index 00000000000..419e2d71313 --- /dev/null +++ b/tests/postgres/sql_loader.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it, vi } from 'vitest'; +import { + loadCommandSql, + loadMigrationSql, +} from '../../src/postgres/sql-loader'; + +describe('PostgreSQL SQL Loader', () => { + it('loads migration SQL files without throwing', () => { + const migration = loadMigrationSql('0001_schema.sql'); + expect(typeof migration).toBe('string'); + expect(migration.length).toBeGreaterThan(0); + expect(migration).toContain('CREATE TABLE'); + }); + + it('loads command SQL files without throwing', () => { + const command = loadCommandSql('add_job'); + expect(typeof command).toBe('string'); + expect(command.length).toBeGreaterThan(0); + }); + + it('caches loaded SQL content for subsequent calls', async () => { + vi.resetModules(); + const readFileSync = vi.fn(() => 'SELECT 1;'); + + vi.doMock('fs', () => ({ + readFileSync, + })); + + const { loadCommandSql: loadCommandSqlFresh } = + await import('../../src/postgres/sql-loader'); + + expect(loadCommandSqlFresh('add_job')).toBe('SELECT 1;'); + expect(loadCommandSqlFresh('add_job')).toBe('SELECT 1;'); + + expect(readFileSync).toHaveBeenCalledTimes(1); + vi.doUnmock('fs'); + }); +});