fase(2): shared infrastructure layer

This commit is contained in:
debian
2026-03-04 16:26:32 -05:00
parent 0e6c0c3655
commit 4a58749048
21 changed files with 1170 additions and 23 deletions

89
dist/shared/infrastructure/Config.js vendored Normal file
View File

@@ -0,0 +1,89 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadConfig = loadConfig;
const zod_1 = require("zod");
const dotenv_1 = __importDefault(require("dotenv"));
dotenv_1.default.config();
const configSchema = zod_1.z.object({
port: zod_1.z.coerce.number().default(3001),
host: zod_1.z.string().default('0.0.0.0'),
nodeEnv: zod_1.z.enum(['development', 'production', 'test']).default('development'),
db: zod_1.z.object({
driver: zod_1.z.enum(['sqlite', 'postgres']).default('sqlite'),
path: zod_1.z.string().default('./data/abe.db'),
url: zod_1.z.string().optional(),
}),
auth: zod_1.z.object({
secret: zod_1.z.string().min(16).default('abe-dev-secret-change-in-prod'),
sessionMaxAge: zod_1.z.coerce.number().default(86400),
}),
storage: zod_1.z.object({
driver: zod_1.z.enum(['local', 's3']).default('local'),
path: zod_1.z.string().default('./data/storage'),
}),
cors: zod_1.z.object({ origin: zod_1.z.string().default('http://localhost:5173') }),
log: zod_1.z.object({ level: zod_1.z.enum(['debug', 'info', 'warn', 'error']).default('info') }),
api: zod_1.z.object({
key: zod_1.z.string().default('abe-dev-key-123'),
rateLimitWindowMs: zod_1.z.coerce.number().default(900000),
rateLimitMax: zod_1.z.coerce.number().default(100),
}),
ai: zod_1.z.object({
provider: zod_1.z.enum(['claude', 'openai', 'ollama', 'none']).default('none'),
apiKey: zod_1.z.string().default(''),
autoEnrich: zod_1.z.coerce.boolean().default(false),
minSeverity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).default('high'),
}),
jobs: zod_1.z.object({
maxConcurrentSessions: zod_1.z.coerce.number().default(3),
pollIntervalMs: zod_1.z.coerce.number().default(1000),
}),
license: zod_1.z.object({ key: zod_1.z.string().default('') }),
});
function loadConfig() {
const raw = {
port: process.env['ABE_PORT'] ?? process.env['PORT'],
host: process.env['ABE_HOST'],
nodeEnv: process.env['NODE_ENV'],
db: {
driver: process.env['ABE_DB_DRIVER'],
path: process.env['ABE_DB_PATH'],
url: process.env['ABE_DB_URL'],
},
auth: {
secret: process.env['ABE_AUTH_SECRET'],
sessionMaxAge: process.env['ABE_SESSION_MAX_AGE'],
},
storage: {
driver: process.env['ABE_STORAGE_DRIVER'],
path: process.env['ABE_STORAGE_PATH'],
},
cors: { origin: process.env['ABE_CORS_ORIGIN'] },
log: { level: process.env['ABE_LOG_LEVEL'] },
api: {
key: process.env['ABE_API_KEY'],
rateLimitWindowMs: process.env['ABE_RATE_LIMIT_WINDOW_MS'],
rateLimitMax: process.env['ABE_RATE_LIMIT_MAX'],
},
ai: {
provider: process.env['ABE_AI_PROVIDER'],
apiKey: process.env['ABE_AI_API_KEY'],
autoEnrich: process.env['ABE_AI_AUTO_ENRICH'],
minSeverity: process.env['ABE_AI_MIN_SEVERITY'],
},
jobs: {
maxConcurrentSessions: process.env['ABE_JOBS_MAX_CONCURRENT'],
pollIntervalMs: process.env['ABE_JOBS_POLL_INTERVAL_MS'],
},
license: { key: process.env['ABE_LICENSE_KEY'] },
};
const result = configSchema.safeParse(raw);
if (!result.success) {
const issues = result.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('\n');
throw new Error(`Invalid configuration:\n${issues}`);
}
return result.data;
}

View File

@@ -0,0 +1,25 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createDatabase = createDatabase;
const kysely_1 = require("kysely");
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
function createDatabase(config) {
if (config.driver === 'postgres') {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { Pool } = require('pg');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { PostgresDialect } = require('kysely');
return new kysely_1.Kysely({
dialect: new PostgresDialect({ pool: new Pool({ connectionString: config.url }) }),
});
}
fs_1.default.mkdirSync(path_1.default.dirname(config.path), { recursive: true });
return new kysely_1.Kysely({
dialect: new kysely_1.SqliteDialect({ database: new better_sqlite3_1.default(config.path) }),
});
}

View File

@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InProcessEventBus = void 0;
const events_1 = require("events");
class InProcessEventBus {
constructor(logger) {
this.emitter = new events_1.EventEmitter();
this.emitter.setMaxListeners(50);
this.logger = logger;
}
async publish(event) {
this.logger.debug({ eventName: event.eventName, aggregateId: event.aggregateId }, 'Publishing domain event');
this.emitter.emit(event.eventName, event);
}
subscribe(eventName, handler) {
this.emitter.on(eventName, (event) => {
handler.handle(event).catch((err) => {
this.logger.error({ eventName, err }, 'Error in event handler');
});
});
}
}
exports.InProcessEventBus = InProcessEventBus;

15
dist/shared/infrastructure/Logger.js vendored Normal file
View File

@@ -0,0 +1,15 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createLogger = createLogger;
const pino_1 = __importDefault(require("pino"));
function createLogger(config) {
return (0, pino_1.default)({
level: config.level,
transport: config.nodeEnv === 'development'
? { target: 'pino-pretty', options: { colorize: true, translateTime: 'HH:MM:ss' } }
: undefined,
});
}

View File

@@ -0,0 +1,48 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalStorageProvider = void 0;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
class LocalStorageProvider {
constructor(basePath) {
this.basePath = basePath;
}
resolve(relativePath) {
return path_1.default.join(this.basePath, relativePath);
}
async save(relativePath, data) {
const fullPath = this.resolve(relativePath);
await promises_1.default.mkdir(path_1.default.dirname(fullPath), { recursive: true });
await promises_1.default.writeFile(fullPath, data);
return fullPath;
}
async get(relativePath) {
try {
return await promises_1.default.readFile(this.resolve(relativePath));
}
catch {
return null;
}
}
async delete(relativePath) {
try {
await promises_1.default.unlink(this.resolve(relativePath));
}
catch {
// ignore missing file
}
}
async exists(relativePath) {
try {
await promises_1.default.access(this.resolve(relativePath));
return true;
}
catch {
return false;
}
}
}
exports.LocalStorageProvider = LocalStorageProvider;

21
dist/shared/infrastructure/index.js vendored Normal file
View File

@@ -0,0 +1,21 @@
"use strict";
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./Config"), exports);
__exportStar(require("./Logger"), exports);
__exportStar(require("./DatabaseConnection"), exports);
__exportStar(require("./InProcessEventBus"), exports);
__exportStar(require("./StorageProvider"), exports);