Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ const {
const {
CredentialRepositoryDocumentDB,
} = require('./credential-repository-documentdb');
const {
CredentialRepositorySqlite,
} = require('./credential-repository-sqlite');
const config = require('../../database/config');

/**
Expand All @@ -13,7 +16,7 @@ const config = require('../../database/config');
*
* Database-specific implementations:
* - MongoDB: Uses String IDs (ObjectId), no conversion needed
* - PostgreSQL: Uses Int IDs, converts String ↔ Int
* - PostgreSQL/SQLite: Uses Int IDs, converts String ↔ Int
*
* All repository methods return String IDs regardless of database type,
* ensuring application layer consistency.
Expand All @@ -38,9 +41,12 @@ function createCredentialRepository() {
case 'documentdb':
return new CredentialRepositoryDocumentDB();

case 'sqlite':
return new CredentialRepositorySqlite();

default:
throw new Error(
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql', 'sqlite'`
);
}
}
Expand All @@ -51,4 +57,5 @@ module.exports = {
CredentialRepositoryMongo,
CredentialRepositoryPostgres,
CredentialRepositoryDocumentDB,
CredentialRepositorySqlite,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* SQLite Credential Repository Adapter
*
* SQLite uses the same schema structure as PostgreSQL:
* - Int IDs with autoincrement
* - JSON stored as String
*
* This implementation extends PostgreSQL since the data access patterns are identical.
* Only difference is the Prisma client used (prisma-sqlite vs prisma-postgresql).
*
* @see CredentialRepositoryPostgres
*/

const { prisma } = require('../../database/prisma');
const { CredentialRepositoryPostgres } = require('./credential-repository-postgres');

class CredentialRepositorySqlite extends CredentialRepositoryPostgres {
constructor() {
// Parent uses this.prisma = prisma from module
// But SQLite uses different prisma instance
super();
this.prisma = prisma;
}
}

module.exports = {
CredentialRepositorySqlite,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Tests for SQLite Credential Repository
* Validates that SQLite repositories work identically to PostgreSQL
*/

const { CredentialRepositorySqlite } = require('./credential-repository-sqlite');

describe('CredentialRepositorySqlite', () => {
describe('extends PostgreSQL repository', () => {
it('should be a class that extends CredentialRepositoryPostgres', () => {
const repository = new CredentialRepositorySqlite();
// Verify it has the expected methods from the parent
expect(typeof repository.findCredentialById).toBe('function');
expect(typeof repository.updateAuthenticationStatus).toBe('function');
expect(typeof repository.deleteCredentialById).toBe('function');
});
});
});
28 changes: 17 additions & 11 deletions packages/core/database/config.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
/**
* Database Configuration
* Manages configuration for Prisma ORM operations
*
* SQLite Support:
* - Falls back to SQLite when no database is configured
* - Useful for local development and AI agent testing
* - Data persisted to .frigg/frigg.db in working directory
*/

/**
* Determines database type from environment or app definition
*
*
* Detection order:
* 1. DB_TYPE environment variable (set for migration handlers)
* 2. App definition (backend/index.js Definition.database configuration)
*
* @returns {'mongodb'|'postgresql'|'documentdb'} Database type
* @returns {'mongodb'|'postgresql'|'documentdb'|'sqlite'} Database type
* @throws {Error} If database type cannot be determined or app definition missing
*/
function getDatabaseType() {

Check failure on line 21 in packages/core/database/config.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=friggframework_frigg&issues=AZ1o_s5LAKkTowA4JzZ8&open=AZ1o_s5LAKkTowA4JzZ8&pullRequest=568
// First, check DB_TYPE environment variable (migration handlers set this)
if (process.env.DB_TYPE) {
return process.env.DB_TYPE;
Expand Down Expand Up @@ -77,15 +82,13 @@

database = backendModule?.Definition?.database;

// If no database configuration, fallback to SQLite for local development
if (!database) {
throw new Error(
'[Frigg] App definition missing database configuration. ' +
`Add database: { postgres: { enable: true } } (or mongoDB/documentDB) to ${backendIndexPath}`
);
return 'sqlite';
}

// Determine database type from enabled database
// Priority order: postgres > mongoDB > documentDB
// Priority order: postgres > mongoDB > documentDB > sqlite
if (database.postgres?.enable === true) {
return 'postgresql';
}
Expand All @@ -96,10 +99,13 @@
return 'documentdb';
}

throw new Error(
'[Frigg] No database enabled in app definition. ' +
'Set one of: database.postgres.enable, database.mongoDB.enable, or database.documentDB.enable to true'
);
// SQLite - for local development and AI agent testing
if (database.sqlite?.enable === true) {
return 'sqlite';
}

// No database enabled - fallback to SQLite for local dev
return 'sqlite';
} catch (error) {
// Re-throw with context if it's our error
if (error.message.includes('[Frigg]')) {
Expand Down
30 changes: 29 additions & 1 deletion packages/core/database/prisma.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
const { logger } = require('./encryption/logger');
const { Cryptor } = require('../encrypt/Cryptor');
const config = require('./config');
const path = require('node:path');
const fs = require('node:fs');

/**
* Ensures DATABASE_URL is set for MongoDB connections
Expand Down Expand Up @@ -90,9 +92,22 @@
PrismaClient = loadPrismaClient('mongodb');
} else if (config.DB_TYPE === 'postgresql') {
PrismaClient = loadPrismaClient('postgresql');
} else if (config.DB_TYPE === 'sqlite') {

Check warning on line 95 in packages/core/database/prisma.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this "===" check; it will always be false. Did you mean to use "=="?

See more on https://sonarcloud.io/project/issues?id=friggframework_frigg&issues=AZ1pD6lOZ4nfRTIF0z4z&open=AZ1pD6lOZ4nfRTIF0z4z&pullRequest=568
// Set default SQLite URL if not provided
if (!process.env.DATABASE_URL) {

Check warning on line 97 in packages/core/database/prisma.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=friggframework_frigg&issues=AZ1o_tDAAKkTowA4JzZ9&open=AZ1o_tDAAKkTowA4JzZ9&pullRequest=568
// Use .frigg/frigg.db in working directory for persistence
// Must use absolute path for SQLite to work reliably
const dbPath = path.resolve('.frigg/frigg.db');
ensureSqliteDirectory();
process.env.DATABASE_URL = `file:${dbPath}`;
logger.info(`Using SQLite database at ${dbPath} (persistent across restarts)`);
} else {
logger.info('Using SQLite database from DATABASE_URL');
}
PrismaClient = loadPrismaClient('sqlite');
} else {
throw new Error(
`Unsupported database type: ${config.DB_TYPE}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
`Unsupported database type: ${config.DB_TYPE}. Supported values: 'mongodb', 'documentdb', 'postgresql', 'sqlite'`
);
}

Expand Down Expand Up @@ -173,10 +188,23 @@
return getPrismaClient();
}

/**
* Ensures .frigg directory exists for SQLite database
* Creates the directory if it doesn't exist
*/
function ensureSqliteDirectory() {
const dirPath = path.resolve('.frigg');
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
logger.debug(`Created .frigg directory at ${dirPath}`);
}
}

module.exports = {
prisma,
connectPrisma,
disconnectPrisma,
getEncryptionConfig,
ensureMongoDbUrl, // Exported for testing
ensureSqliteDirectory, // Exported for testing
};
68 changes: 67 additions & 1 deletion packages/core/database/prisma.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
* Validates DATABASE_URL configuration with MONGO_URI fallback
*/

const { ensureMongoDbUrl } = require('./prisma');
const { ensureMongoDbUrl, ensureSqliteDirectory } = require('./prisma');
const path = require('node:path');
const fs = require('node:fs');

describe('Prisma MongoDB Adapter', () => {
let originalEnv;
Expand Down Expand Up @@ -63,3 +65,67 @@ describe('Prisma MongoDB Adapter', () => {
});
});
});

describe('Prisma SQLite Adapter', () => {
const testDir = path.join(__dirname, 'test-sqlite-temp');

beforeAll(() => {
// Create test directory
if (!fs.existsSync(testDir)) {
fs.mkdirSync(testDir, { recursive: true });
}
});

afterAll(() => {
// Clean up test directory
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true, force: true });
}
});

describe('ensureSqliteDirectory()', () => {
it('should create .frigg directory in working directory', () => {
// Change to test directory
const originalCwd = process.cwd();
process.chdir(testDir);

try {
const friggDir = path.join(testDir, '.frigg');

// Ensure directory doesn't exist
if (fs.existsSync(friggDir)) {
fs.rmSync(friggDir, { recursive: true });
}

// Call ensureSqliteDirectory
ensureSqliteDirectory();

// Verify directory was created
expect(fs.existsSync(friggDir)).toBe(true);
expect(fs.statSync(friggDir).isDirectory()).toBe(true);
} finally {
// Restore original directory
process.chdir(originalCwd);
}
});

it('should not throw if directory already exists', () => {
const originalCwd = process.cwd();
process.chdir(testDir);

try {
const friggDir = path.join(testDir, '.frigg');

// Ensure directory exists
if (!fs.existsSync(friggDir)) {
fs.mkdirSync(friggDir);
}

// Should not throw
expect(() => ensureSqliteDirectory()).not.toThrow();
} finally {
process.chdir(originalCwd);
}
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { HealthCheckRepositoryMongoDB } = require('./health-check-repository-mongodb');
const { HealthCheckRepositoryPostgreSQL } = require('./health-check-repository-postgres');
const { HealthCheckRepositoryDocumentDB } = require('./health-check-repository-documentdb');
const { HealthCheckRepositorySqlite } = require('./health-check-repository-sqlite');
const config = require('../config');

/**
Expand Down Expand Up @@ -33,9 +34,12 @@ function createHealthCheckRepository({ prismaClient } = {}) {
case 'documentdb':
return new HealthCheckRepositoryDocumentDB({ prismaClient });

case 'sqlite':
return new HealthCheckRepositorySqlite({ prismaClient });

default:
throw new Error(
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql', 'sqlite'`
);
}
}
Expand All @@ -45,4 +49,5 @@ module.exports = {
HealthCheckRepositoryMongoDB,
HealthCheckRepositoryPostgreSQL,
HealthCheckRepositoryDocumentDB,
HealthCheckRepositorySqlite,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* SQLite Health Check Repository
*
* SQLite uses the same Prisma schema structure as PostgreSQL:
* - Int IDs with autoincrement
* - JSON stored as String
*
* This implementation extends PostgreSQL since the data access patterns are identical.
*
* @see HealthCheckRepositoryPostgreSQL
*/

const { HealthCheckRepositoryPostgreSQL } = require('./health-check-repository-postgres');

class HealthCheckRepositorySqlite extends HealthCheckRepositoryPostgreSQL {
/**
* @param {Object} params
* @param {Object} params.prismaClient - Prisma client instance
*/
constructor({ prismaClient }) {
super({ prismaClient });
}
}

module.exports = { HealthCheckRepositorySqlite };
16 changes: 15 additions & 1 deletion packages/core/handlers/database-migration-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ function getSchemaPath() {
return schemaPath;
}

// Check if SQLite is enabled (file-based or .db extension)
if (process.env.DATABASE_URL?.includes('sqlite') ||
process.env.DATABASE_URL?.includes('.db') ||
process.env.DB_TYPE === 'sqlite') {
const schemaPath = `${baseSchemaPath}/prisma-sqlite/schema.prisma`;
console.log(`Using SQLite schema: ${schemaPath}`);
return schemaPath;
}

// Default to PostgreSQL
console.log('DATABASE_URL not set or database type unknown, defaulting to PostgreSQL');
return `${baseSchemaPath}/prisma-postgresql/schema.prisma`;
Expand Down Expand Up @@ -180,7 +189,12 @@ exports.handler = async (event, context) => {
const schemaPath = getSchemaPath();

// Execute migration
const exitCode = await executePrismaMigration(command, schemaPath);
// For SQLite, use 'db push' instead of 'migrate deploy' (SQLite doesn't support migrations)
const effectiveCommand = (schemaPath.includes('prisma-sqlite') && command === 'deploy')
? 'deploy' // For SQLite we'll use migrate deploy which works fine
: command;

const exitCode = await executePrismaMigration(effectiveCommand, schemaPath);

const duration = Date.now() - startTime;

Expand Down
Loading
Loading