archicore 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/interactive.js +51 -19
- package/dist/github/github-service.d.ts +5 -1
- package/dist/github/github-service.js +21 -3
- package/dist/semantic-memory/embedding-service.d.ts +8 -1
- package/dist/semantic-memory/embedding-service.js +141 -47
- package/dist/server/index.js +66 -1
- package/dist/server/routes/admin.js +149 -1
- package/dist/server/routes/auth.js +46 -0
- package/dist/server/routes/github.js +17 -4
- package/dist/server/services/audit-service.d.ts +88 -0
- package/dist/server/services/audit-service.js +380 -0
- package/dist/server/services/auth-service.d.ts +11 -5
- package/dist/server/services/auth-service.js +299 -52
- package/dist/server/services/cache.d.ts +77 -0
- package/dist/server/services/cache.js +245 -0
- package/dist/server/services/database.d.ts +43 -0
- package/dist/server/services/database.js +221 -0
- package/package.json +17 -2
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArchiCore Redis Cache Service
|
|
3
|
+
*
|
|
4
|
+
* Provides caching for API responses to improve performance
|
|
5
|
+
*/
|
|
6
|
+
import Redis from 'ioredis';
|
|
7
|
+
import { Logger } from '../../utils/logger.js';
|
|
8
|
+
const DEFAULT_TTL = 300; // 5 minutes
|
|
9
|
+
const CACHE_PREFIX = 'archicore:';
|
|
10
|
+
class CacheService {
|
|
11
|
+
client = null;
|
|
12
|
+
enabled = false;
|
|
13
|
+
memoryCache = new Map();
|
|
14
|
+
async connect() {
|
|
15
|
+
const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
|
|
16
|
+
try {
|
|
17
|
+
this.client = new Redis(redisUrl, {
|
|
18
|
+
maxRetriesPerRequest: 3,
|
|
19
|
+
retryStrategy: (times) => {
|
|
20
|
+
if (times > 3) {
|
|
21
|
+
Logger.warn('Redis connection failed, using memory cache fallback');
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return Math.min(times * 100, 3000);
|
|
25
|
+
},
|
|
26
|
+
lazyConnect: true,
|
|
27
|
+
});
|
|
28
|
+
await this.client.connect();
|
|
29
|
+
this.client.on('error', (err) => {
|
|
30
|
+
Logger.error('Redis error:', err.message);
|
|
31
|
+
this.enabled = false;
|
|
32
|
+
});
|
|
33
|
+
this.client.on('connect', () => {
|
|
34
|
+
Logger.info('Redis connected');
|
|
35
|
+
this.enabled = true;
|
|
36
|
+
});
|
|
37
|
+
this.client.on('close', () => {
|
|
38
|
+
Logger.warn('Redis connection closed');
|
|
39
|
+
this.enabled = false;
|
|
40
|
+
});
|
|
41
|
+
// Test connection
|
|
42
|
+
await this.client.ping();
|
|
43
|
+
this.enabled = true;
|
|
44
|
+
Logger.success('Redis cache enabled');
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
Logger.warn('Redis not available, using memory cache');
|
|
48
|
+
this.enabled = false;
|
|
49
|
+
this.client = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
getKey(key, prefix) {
|
|
53
|
+
return `${CACHE_PREFIX}${prefix || ''}${key}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get value from cache
|
|
57
|
+
*/
|
|
58
|
+
async get(key, prefix) {
|
|
59
|
+
const fullKey = this.getKey(key, prefix);
|
|
60
|
+
// Try Redis first
|
|
61
|
+
if (this.enabled && this.client) {
|
|
62
|
+
try {
|
|
63
|
+
const data = await this.client.get(fullKey);
|
|
64
|
+
if (data) {
|
|
65
|
+
return JSON.parse(data);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
Logger.error('Cache get error:', error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Fallback to memory cache
|
|
73
|
+
const cached = this.memoryCache.get(fullKey);
|
|
74
|
+
if (cached && cached.expires > Date.now()) {
|
|
75
|
+
return cached.data;
|
|
76
|
+
}
|
|
77
|
+
// Clean up expired entry
|
|
78
|
+
if (cached) {
|
|
79
|
+
this.memoryCache.delete(fullKey);
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Set value in cache
|
|
85
|
+
*/
|
|
86
|
+
async set(key, value, options = {}) {
|
|
87
|
+
const fullKey = this.getKey(key, options.prefix);
|
|
88
|
+
const ttl = options.ttl || DEFAULT_TTL;
|
|
89
|
+
const serialized = JSON.stringify(value);
|
|
90
|
+
// Try Redis first
|
|
91
|
+
if (this.enabled && this.client) {
|
|
92
|
+
try {
|
|
93
|
+
await this.client.setex(fullKey, ttl, serialized);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
Logger.error('Cache set error:', error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Fallback to memory cache
|
|
101
|
+
this.memoryCache.set(fullKey, {
|
|
102
|
+
data: value,
|
|
103
|
+
expires: Date.now() + (ttl * 1000),
|
|
104
|
+
});
|
|
105
|
+
// Clean up old memory cache entries periodically
|
|
106
|
+
if (this.memoryCache.size > 1000) {
|
|
107
|
+
this.cleanupMemoryCache();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Delete value from cache
|
|
112
|
+
*/
|
|
113
|
+
async del(key, prefix) {
|
|
114
|
+
const fullKey = this.getKey(key, prefix);
|
|
115
|
+
if (this.enabled && this.client) {
|
|
116
|
+
try {
|
|
117
|
+
await this.client.del(fullKey);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
Logger.error('Cache del error:', error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
this.memoryCache.delete(fullKey);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Delete all keys matching pattern
|
|
127
|
+
*/
|
|
128
|
+
async delPattern(pattern) {
|
|
129
|
+
const fullPattern = this.getKey(pattern);
|
|
130
|
+
if (this.enabled && this.client) {
|
|
131
|
+
try {
|
|
132
|
+
const keys = await this.client.keys(fullPattern);
|
|
133
|
+
if (keys.length > 0) {
|
|
134
|
+
await this.client.del(...keys);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
Logger.error('Cache delPattern error:', error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Clean memory cache matching pattern
|
|
142
|
+
const regex = new RegExp(fullPattern.replace(/\*/g, '.*'));
|
|
143
|
+
for (const key of this.memoryCache.keys()) {
|
|
144
|
+
if (regex.test(key)) {
|
|
145
|
+
this.memoryCache.delete(key);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Clear all cache
|
|
151
|
+
*/
|
|
152
|
+
async flush() {
|
|
153
|
+
if (this.enabled && this.client) {
|
|
154
|
+
try {
|
|
155
|
+
const keys = await this.client.keys(`${CACHE_PREFIX}*`);
|
|
156
|
+
if (keys.length > 0) {
|
|
157
|
+
await this.client.del(...keys);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
Logger.error('Cache flush error:', error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
this.memoryCache.clear();
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get or set cache with callback
|
|
168
|
+
*/
|
|
169
|
+
async getOrSet(key, callback, options = {}) {
|
|
170
|
+
const cached = await this.get(key, options.prefix);
|
|
171
|
+
if (cached !== null) {
|
|
172
|
+
return cached;
|
|
173
|
+
}
|
|
174
|
+
const value = await callback();
|
|
175
|
+
await this.set(key, value, options);
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check if cache is available
|
|
180
|
+
*/
|
|
181
|
+
isEnabled() {
|
|
182
|
+
return this.enabled;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get cache stats
|
|
186
|
+
*/
|
|
187
|
+
async getStats() {
|
|
188
|
+
let keys = 0;
|
|
189
|
+
if (this.enabled && this.client) {
|
|
190
|
+
try {
|
|
191
|
+
const allKeys = await this.client.keys(`${CACHE_PREFIX}*`);
|
|
192
|
+
keys = allKeys.length;
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
Logger.error('Cache stats error:', error);
|
|
196
|
+
}
|
|
197
|
+
return { enabled: true, type: 'redis', keys };
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
enabled: false,
|
|
201
|
+
type: 'memory',
|
|
202
|
+
keys: this.memoryCache.size
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Cleanup expired memory cache entries
|
|
207
|
+
*/
|
|
208
|
+
cleanupMemoryCache() {
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
for (const [key, value] of this.memoryCache.entries()) {
|
|
211
|
+
if (value.expires < now) {
|
|
212
|
+
this.memoryCache.delete(key);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Disconnect from Redis
|
|
218
|
+
*/
|
|
219
|
+
async disconnect() {
|
|
220
|
+
if (this.client) {
|
|
221
|
+
await this.client.quit();
|
|
222
|
+
this.client = null;
|
|
223
|
+
this.enabled = false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Singleton instance
|
|
228
|
+
export const cache = new CacheService();
|
|
229
|
+
// Cache key generators
|
|
230
|
+
export const cacheKeys = {
|
|
231
|
+
project: (projectId) => `project:${projectId}`,
|
|
232
|
+
projectAnalysis: (projectId) => `analysis:${projectId}`,
|
|
233
|
+
projectMetrics: (projectId) => `metrics:${projectId}`,
|
|
234
|
+
projectSearch: (projectId, query) => `search:${projectId}:${query}`,
|
|
235
|
+
user: (userId) => `user:${userId}`,
|
|
236
|
+
userProjects: (userId) => `user-projects:${userId}`,
|
|
237
|
+
};
|
|
238
|
+
// TTL presets (in seconds)
|
|
239
|
+
export const cacheTTL = {
|
|
240
|
+
short: 60, // 1 minute
|
|
241
|
+
medium: 300, // 5 minutes
|
|
242
|
+
long: 3600, // 1 hour
|
|
243
|
+
day: 86400, // 24 hours
|
|
244
|
+
};
|
|
245
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL Database Service for ArchiCore
|
|
3
|
+
*
|
|
4
|
+
* Connection pooling and schema management
|
|
5
|
+
*/
|
|
6
|
+
import pg from 'pg';
|
|
7
|
+
declare class DatabaseService {
|
|
8
|
+
private pool;
|
|
9
|
+
private initialized;
|
|
10
|
+
private initPromise;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize database connection pool
|
|
13
|
+
*/
|
|
14
|
+
init(): Promise<void>;
|
|
15
|
+
private _init;
|
|
16
|
+
/**
|
|
17
|
+
* Initialize database schema
|
|
18
|
+
*/
|
|
19
|
+
private initSchema;
|
|
20
|
+
/**
|
|
21
|
+
* Check if database is available
|
|
22
|
+
*/
|
|
23
|
+
isAvailable(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Get a client from the pool
|
|
26
|
+
*/
|
|
27
|
+
getClient(): Promise<pg.PoolClient>;
|
|
28
|
+
/**
|
|
29
|
+
* Execute a query
|
|
30
|
+
*/
|
|
31
|
+
query<T extends pg.QueryResultRow = any>(text: string, params?: any[]): Promise<pg.QueryResult<T>>;
|
|
32
|
+
/**
|
|
33
|
+
* Execute a transaction
|
|
34
|
+
*/
|
|
35
|
+
transaction<T>(callback: (client: pg.PoolClient) => Promise<T>): Promise<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Close the connection pool
|
|
38
|
+
*/
|
|
39
|
+
close(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
export declare const db: DatabaseService;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL Database Service for ArchiCore
|
|
3
|
+
*
|
|
4
|
+
* Connection pooling and schema management
|
|
5
|
+
*/
|
|
6
|
+
import pg from 'pg';
|
|
7
|
+
import { Logger } from '../../utils/logger.js';
|
|
8
|
+
const { Pool } = pg;
|
|
9
|
+
// Database schema version for migrations
|
|
10
|
+
const SCHEMA_VERSION = 1;
|
|
11
|
+
// SQL schema for ArchiCore
|
|
12
|
+
const SCHEMA_SQL = `
|
|
13
|
+
-- Users table
|
|
14
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
15
|
+
id VARCHAR(64) PRIMARY KEY,
|
|
16
|
+
email VARCHAR(255) UNIQUE NOT NULL,
|
|
17
|
+
username VARCHAR(100) NOT NULL,
|
|
18
|
+
password_hash VARCHAR(255),
|
|
19
|
+
avatar TEXT,
|
|
20
|
+
tier VARCHAR(20) NOT NULL DEFAULT 'free',
|
|
21
|
+
provider VARCHAR(20) NOT NULL DEFAULT 'email',
|
|
22
|
+
provider_id VARCHAR(255),
|
|
23
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
24
|
+
last_login_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
25
|
+
|
|
26
|
+
-- Usage tracking (reset daily)
|
|
27
|
+
requests_today INTEGER DEFAULT 0,
|
|
28
|
+
full_analysis_today INTEGER DEFAULT 0,
|
|
29
|
+
projects_today INTEGER DEFAULT 0,
|
|
30
|
+
usage_reset_date DATE DEFAULT CURRENT_DATE,
|
|
31
|
+
|
|
32
|
+
-- Subscription info
|
|
33
|
+
subscription_id VARCHAR(64),
|
|
34
|
+
subscription_status VARCHAR(20),
|
|
35
|
+
subscription_end_date TIMESTAMP WITH TIME ZONE
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
-- Sessions table
|
|
39
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
40
|
+
token VARCHAR(128) PRIMARY KEY,
|
|
41
|
+
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
42
|
+
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
43
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- Audit logs table
|
|
47
|
+
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
48
|
+
id VARCHAR(64) PRIMARY KEY,
|
|
49
|
+
user_id VARCHAR(64) REFERENCES users(id) ON DELETE SET NULL,
|
|
50
|
+
username VARCHAR(100),
|
|
51
|
+
action VARCHAR(50) NOT NULL,
|
|
52
|
+
severity VARCHAR(20) NOT NULL DEFAULT 'info',
|
|
53
|
+
ip VARCHAR(45),
|
|
54
|
+
user_agent TEXT,
|
|
55
|
+
details JSONB,
|
|
56
|
+
success BOOLEAN DEFAULT true,
|
|
57
|
+
error_message TEXT,
|
|
58
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
-- Projects table (for future use)
|
|
62
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
63
|
+
id VARCHAR(64) PRIMARY KEY,
|
|
64
|
+
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
65
|
+
name VARCHAR(255) NOT NULL,
|
|
66
|
+
description TEXT,
|
|
67
|
+
repo_url TEXT,
|
|
68
|
+
repo_provider VARCHAR(20),
|
|
69
|
+
repo_id VARCHAR(255),
|
|
70
|
+
webhook_secret TEXT,
|
|
71
|
+
last_analyzed_at TIMESTAMP WITH TIME ZONE,
|
|
72
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
73
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
-- Schema version tracking
|
|
77
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
78
|
+
version INTEGER PRIMARY KEY,
|
|
79
|
+
applied_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
-- Indexes for performance
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id);
|
|
86
|
+
CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs(action);
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at);
|
|
88
|
+
CREATE INDEX IF NOT EXISTS idx_projects_user_id ON projects(user_id);
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_users_provider ON users(provider, provider_id);
|
|
91
|
+
`;
|
|
92
|
+
class DatabaseService {
|
|
93
|
+
pool = null;
|
|
94
|
+
initialized = false;
|
|
95
|
+
initPromise = null;
|
|
96
|
+
/**
|
|
97
|
+
* Initialize database connection pool
|
|
98
|
+
*/
|
|
99
|
+
async init() {
|
|
100
|
+
if (this.initialized)
|
|
101
|
+
return;
|
|
102
|
+
if (this.initPromise)
|
|
103
|
+
return this.initPromise;
|
|
104
|
+
this.initPromise = this._init();
|
|
105
|
+
return this.initPromise;
|
|
106
|
+
}
|
|
107
|
+
async _init() {
|
|
108
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
109
|
+
if (!databaseUrl) {
|
|
110
|
+
Logger.warn('DATABASE_URL not set - using JSON file storage');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
this.pool = new Pool({
|
|
115
|
+
connectionString: databaseUrl,
|
|
116
|
+
max: 20, // Maximum pool size
|
|
117
|
+
idleTimeoutMillis: 30000,
|
|
118
|
+
connectionTimeoutMillis: 5000,
|
|
119
|
+
});
|
|
120
|
+
// Test connection
|
|
121
|
+
const client = await this.pool.connect();
|
|
122
|
+
await client.query('SELECT NOW()');
|
|
123
|
+
client.release();
|
|
124
|
+
// Initialize schema
|
|
125
|
+
await this.initSchema();
|
|
126
|
+
this.initialized = true;
|
|
127
|
+
Logger.success('PostgreSQL connected with connection pool (max: 20)');
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
Logger.error('PostgreSQL connection failed:', error);
|
|
131
|
+
this.pool = null;
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Initialize database schema
|
|
137
|
+
*/
|
|
138
|
+
async initSchema() {
|
|
139
|
+
if (!this.pool)
|
|
140
|
+
return;
|
|
141
|
+
try {
|
|
142
|
+
// Create schema
|
|
143
|
+
await this.pool.query(SCHEMA_SQL);
|
|
144
|
+
// Check/update schema version
|
|
145
|
+
const result = await this.pool.query('SELECT version FROM schema_version ORDER BY version DESC LIMIT 1');
|
|
146
|
+
if (result.rows.length === 0) {
|
|
147
|
+
await this.pool.query('INSERT INTO schema_version (version) VALUES ($1)', [SCHEMA_VERSION]);
|
|
148
|
+
Logger.info(`Database schema initialized (v${SCHEMA_VERSION})`);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const currentVersion = result.rows[0].version;
|
|
152
|
+
if (currentVersion < SCHEMA_VERSION) {
|
|
153
|
+
// Run migrations here if needed
|
|
154
|
+
await this.pool.query('INSERT INTO schema_version (version) VALUES ($1)', [SCHEMA_VERSION]);
|
|
155
|
+
Logger.info(`Database schema migrated to v${SCHEMA_VERSION}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
Logger.error('Schema initialization failed:', error);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Check if database is available
|
|
166
|
+
*/
|
|
167
|
+
isAvailable() {
|
|
168
|
+
return this.pool !== null && this.initialized;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get a client from the pool
|
|
172
|
+
*/
|
|
173
|
+
async getClient() {
|
|
174
|
+
if (!this.pool) {
|
|
175
|
+
throw new Error('Database not initialized');
|
|
176
|
+
}
|
|
177
|
+
return this.pool.connect();
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Execute a query
|
|
181
|
+
*/
|
|
182
|
+
async query(text, params) {
|
|
183
|
+
if (!this.pool) {
|
|
184
|
+
throw new Error('Database not initialized');
|
|
185
|
+
}
|
|
186
|
+
return this.pool.query(text, params);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Execute a transaction
|
|
190
|
+
*/
|
|
191
|
+
async transaction(callback) {
|
|
192
|
+
const client = await this.getClient();
|
|
193
|
+
try {
|
|
194
|
+
await client.query('BEGIN');
|
|
195
|
+
const result = await callback(client);
|
|
196
|
+
await client.query('COMMIT');
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
await client.query('ROLLBACK');
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
client.release();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Close the connection pool
|
|
209
|
+
*/
|
|
210
|
+
async close() {
|
|
211
|
+
if (this.pool) {
|
|
212
|
+
await this.pool.end();
|
|
213
|
+
this.pool = null;
|
|
214
|
+
this.initialized = false;
|
|
215
|
+
Logger.info('Database connection pool closed');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Export singleton instance
|
|
220
|
+
export const db = new DatabaseService();
|
|
221
|
+
//# sourceMappingURL=database.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archicore",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "AI Software Architect - code analysis, impact prediction, semantic search",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,17 @@
|
|
|
25
25
|
"cli": "tsx src/cli.ts",
|
|
26
26
|
"index": "tsx src/cli.ts index",
|
|
27
27
|
"analyze": "tsx src/cli.ts analyze",
|
|
28
|
-
"impact": "tsx src/cli.ts impact"
|
|
28
|
+
"impact": "tsx src/cli.ts impact",
|
|
29
|
+
"docker:build": "docker build -t archicore .",
|
|
30
|
+
"docker:up": "docker compose up -d",
|
|
31
|
+
"docker:down": "docker compose down",
|
|
32
|
+
"docker:logs": "docker compose logs -f archicore",
|
|
33
|
+
"docker:dev": "docker compose -f docker-compose.dev.yml up -d",
|
|
34
|
+
"docker:dev:down": "docker compose -f docker-compose.dev.yml down",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"docs": "npm --prefix docs-public run start",
|
|
37
|
+
"docs:build": "npm --prefix docs-public run build",
|
|
38
|
+
"docs:install": "npm --prefix docs-public install"
|
|
29
39
|
},
|
|
30
40
|
"keywords": [
|
|
31
41
|
"ai",
|
|
@@ -42,6 +52,7 @@
|
|
|
42
52
|
"@qdrant/js-client-rest": "^1.11.0",
|
|
43
53
|
"@types/unzipper": "^0.10.11",
|
|
44
54
|
"adm-zip": "^0.5.16",
|
|
55
|
+
"bcrypt": "^5.1.1",
|
|
45
56
|
"boxen": "^8.0.1",
|
|
46
57
|
"chalk": "^5.4.1",
|
|
47
58
|
"cli-progress": "^3.12.0",
|
|
@@ -55,10 +66,12 @@
|
|
|
55
66
|
"figures": "^6.1.0",
|
|
56
67
|
"glob": "^11.0.0",
|
|
57
68
|
"helmet": "^8.0.0",
|
|
69
|
+
"ioredis": "^5.9.1",
|
|
58
70
|
"mime-types": "^3.0.2",
|
|
59
71
|
"morgan": "^1.10.1",
|
|
60
72
|
"multer": "^2.0.2",
|
|
61
73
|
"openai": "^4.73.0",
|
|
74
|
+
"pg": "^8.13.0",
|
|
62
75
|
"ora": "^8.1.1",
|
|
63
76
|
"tree-sitter": "^0.21.1",
|
|
64
77
|
"tree-sitter-javascript": "^0.21.4",
|
|
@@ -70,6 +83,7 @@
|
|
|
70
83
|
},
|
|
71
84
|
"devDependencies": {
|
|
72
85
|
"@types/adm-zip": "^0.5.7",
|
|
86
|
+
"@types/bcrypt": "^5.0.2",
|
|
73
87
|
"@types/cli-progress": "^3.11.6",
|
|
74
88
|
"@types/compression": "^1.7.5",
|
|
75
89
|
"@types/cors": "^2.8.19",
|
|
@@ -78,6 +92,7 @@
|
|
|
78
92
|
"@types/morgan": "^1.9.10",
|
|
79
93
|
"@types/multer": "^2.0.0",
|
|
80
94
|
"@types/node": "^22.10.2",
|
|
95
|
+
"@types/pg": "^8.11.6",
|
|
81
96
|
"tsx": "^4.19.2",
|
|
82
97
|
"typescript": "^5.7.2"
|
|
83
98
|
}
|