relq 1.0.2 → 1.0.4
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/cjs/cli/commands/add.cjs +403 -27
- package/dist/cjs/cli/commands/branch.cjs +13 -23
- package/dist/cjs/cli/commands/checkout.cjs +16 -29
- package/dist/cjs/cli/commands/cherry-pick.cjs +3 -4
- package/dist/cjs/cli/commands/commit.cjs +21 -29
- package/dist/cjs/cli/commands/diff.cjs +28 -32
- package/dist/cjs/cli/commands/export.cjs +7 -7
- package/dist/cjs/cli/commands/fetch.cjs +15 -21
- package/dist/cjs/cli/commands/generate.cjs +28 -54
- package/dist/cjs/cli/commands/history.cjs +19 -40
- package/dist/cjs/cli/commands/import.cjs +34 -41
- package/dist/cjs/cli/commands/init.cjs +69 -59
- package/dist/cjs/cli/commands/introspect.cjs +4 -8
- package/dist/cjs/cli/commands/log.cjs +26 -32
- package/dist/cjs/cli/commands/merge.cjs +24 -41
- package/dist/cjs/cli/commands/migrate.cjs +12 -25
- package/dist/cjs/cli/commands/pull.cjs +216 -106
- package/dist/cjs/cli/commands/push.cjs +35 -75
- package/dist/cjs/cli/commands/remote.cjs +2 -1
- package/dist/cjs/cli/commands/reset.cjs +22 -43
- package/dist/cjs/cli/commands/resolve.cjs +12 -14
- package/dist/cjs/cli/commands/rollback.cjs +16 -38
- package/dist/cjs/cli/commands/stash.cjs +5 -7
- package/dist/cjs/cli/commands/status.cjs +5 -10
- package/dist/cjs/cli/commands/sync.cjs +30 -50
- package/dist/cjs/cli/commands/tag.cjs +3 -4
- package/dist/cjs/cli/index.cjs +72 -9
- package/dist/cjs/cli/utils/change-tracker.cjs +107 -3
- package/dist/cjs/cli/utils/cli-utils.cjs +217 -0
- package/dist/cjs/cli/utils/config-loader.cjs +34 -8
- package/dist/cjs/cli/utils/fast-introspect.cjs +109 -3
- package/dist/cjs/cli/utils/git-utils.cjs +42 -161
- package/dist/cjs/cli/utils/pool-manager.cjs +156 -0
- package/dist/cjs/cli/utils/project-root.cjs +56 -5
- package/dist/cjs/cli/utils/relqignore.cjs +1 -0
- package/dist/cjs/cli/utils/repo-manager.cjs +47 -0
- package/dist/cjs/cli/utils/schema-comparator.cjs +301 -11
- package/dist/cjs/cli/utils/schema-diff.cjs +202 -1
- package/dist/cjs/cli/utils/schema-hash.cjs +2 -1
- package/dist/cjs/cli/utils/schema-introspect.cjs +7 -3
- package/dist/cjs/cli/utils/snapshot-manager.cjs +1 -0
- package/dist/cjs/cli/utils/spinner.cjs +14 -106
- package/dist/cjs/cli/utils/sql-generator.cjs +10 -2
- package/dist/cjs/cli/utils/type-generator.cjs +28 -16
- package/dist/config.d.ts +16 -6
- package/dist/esm/cli/commands/add.js +372 -29
- package/dist/esm/cli/commands/branch.js +14 -24
- package/dist/esm/cli/commands/checkout.js +16 -29
- package/dist/esm/cli/commands/cherry-pick.js +3 -4
- package/dist/esm/cli/commands/commit.js +22 -30
- package/dist/esm/cli/commands/diff.js +6 -10
- package/dist/esm/cli/commands/export.js +8 -8
- package/dist/esm/cli/commands/fetch.js +14 -20
- package/dist/esm/cli/commands/generate.js +28 -54
- package/dist/esm/cli/commands/history.js +11 -32
- package/dist/esm/cli/commands/import.js +35 -42
- package/dist/esm/cli/commands/init.js +65 -55
- package/dist/esm/cli/commands/introspect.js +4 -8
- package/dist/esm/cli/commands/log.js +6 -12
- package/dist/esm/cli/commands/merge.js +20 -37
- package/dist/esm/cli/commands/migrate.js +12 -25
- package/dist/esm/cli/commands/pull.js +204 -94
- package/dist/esm/cli/commands/push.js +21 -61
- package/dist/esm/cli/commands/remote.js +2 -1
- package/dist/esm/cli/commands/reset.js +16 -37
- package/dist/esm/cli/commands/resolve.js +13 -15
- package/dist/esm/cli/commands/rollback.js +16 -38
- package/dist/esm/cli/commands/stash.js +6 -8
- package/dist/esm/cli/commands/status.js +6 -11
- package/dist/esm/cli/commands/sync.js +30 -50
- package/dist/esm/cli/commands/tag.js +3 -4
- package/dist/esm/cli/index.js +72 -9
- package/dist/esm/cli/utils/change-tracker.js +107 -3
- package/dist/esm/cli/utils/cli-utils.js +169 -0
- package/dist/esm/cli/utils/config-loader.js +34 -8
- package/dist/esm/cli/utils/fast-introspect.js +109 -3
- package/dist/esm/cli/utils/git-utils.js +2 -124
- package/dist/esm/cli/utils/pool-manager.js +114 -0
- package/dist/esm/cli/utils/project-root.js +55 -5
- package/dist/esm/cli/utils/relqignore.js +1 -0
- package/dist/esm/cli/utils/repo-manager.js +42 -0
- package/dist/esm/cli/utils/schema-comparator.js +301 -11
- package/dist/esm/cli/utils/schema-diff.js +202 -1
- package/dist/esm/cli/utils/schema-hash.js +2 -1
- package/dist/esm/cli/utils/schema-introspect.js +7 -3
- package/dist/esm/cli/utils/snapshot-manager.js +1 -0
- package/dist/esm/cli/utils/spinner.js +1 -101
- package/dist/esm/cli/utils/sql-generator.js +10 -2
- package/dist/esm/cli/utils/type-generator.js +28 -16
- package/dist/index.d.ts +25 -8
- package/dist/schema-builder.d.ts +18 -7
- package/package.json +1 -1
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
function parseOptionsArray(options) {
|
|
2
|
+
if (!options)
|
|
3
|
+
return {};
|
|
4
|
+
const result = {};
|
|
5
|
+
for (const opt of options) {
|
|
6
|
+
const eqIdx = opt.indexOf('=');
|
|
7
|
+
if (eqIdx > 0) {
|
|
8
|
+
result[opt.substring(0, eqIdx)] = opt.substring(eqIdx + 1);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
1
13
|
export async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
2
14
|
const { includeFunctions = false, includeTriggers = false } = options || {};
|
|
3
15
|
const { Pool } = await import("../../addon/pg/index.js");
|
|
@@ -218,7 +230,7 @@ export async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
218
230
|
const tables = [];
|
|
219
231
|
for (const row of tablesResult.rows) {
|
|
220
232
|
const tableName = row.table_name;
|
|
221
|
-
if (tableName.startsWith('_relq'))
|
|
233
|
+
if (tableName.startsWith('_relq') || tableName.startsWith('_kuery'))
|
|
222
234
|
continue;
|
|
223
235
|
tables.push({
|
|
224
236
|
name: tableName,
|
|
@@ -340,6 +352,99 @@ export async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
340
352
|
isEnabled: t.is_enabled,
|
|
341
353
|
}));
|
|
342
354
|
}
|
|
355
|
+
onProgress?.('fetching_collations');
|
|
356
|
+
const collationsResult = await pool.query(`
|
|
357
|
+
SELECT
|
|
358
|
+
c.collname as name,
|
|
359
|
+
n.nspname as schema,
|
|
360
|
+
c.collprovider as provider,
|
|
361
|
+
c.collcollate as lc_collate,
|
|
362
|
+
c.collctype as lc_ctype,
|
|
363
|
+
c.collisdeterministic as deterministic
|
|
364
|
+
FROM pg_collation c
|
|
365
|
+
JOIN pg_namespace n ON c.collnamespace = n.oid
|
|
366
|
+
WHERE n.nspname = 'public'
|
|
367
|
+
ORDER BY c.collname;
|
|
368
|
+
`);
|
|
369
|
+
const collations = collationsResult.rows.map(c => ({
|
|
370
|
+
name: c.name,
|
|
371
|
+
schema: c.schema,
|
|
372
|
+
provider: c.provider === 'i' ? 'icu' : c.provider === 'c' ? 'libc' : 'default',
|
|
373
|
+
lcCollate: c.lc_collate,
|
|
374
|
+
lcCtype: c.lc_ctype,
|
|
375
|
+
deterministic: c.deterministic,
|
|
376
|
+
}));
|
|
377
|
+
onProgress?.('fetching_foreign_servers');
|
|
378
|
+
const foreignServersResult = await pool.query(`
|
|
379
|
+
SELECT
|
|
380
|
+
s.srvname as name,
|
|
381
|
+
f.fdwname as fdw,
|
|
382
|
+
s.srvoptions as options
|
|
383
|
+
FROM pg_foreign_server s
|
|
384
|
+
JOIN pg_foreign_data_wrapper f ON s.srvfdw = f.oid
|
|
385
|
+
ORDER BY s.srvname;
|
|
386
|
+
`);
|
|
387
|
+
const foreignServers = foreignServersResult.rows.map(s => ({
|
|
388
|
+
name: s.name,
|
|
389
|
+
foreignDataWrapper: s.fdw,
|
|
390
|
+
options: parseOptionsArray(s.options),
|
|
391
|
+
}));
|
|
392
|
+
onProgress?.('fetching_foreign_tables');
|
|
393
|
+
const foreignTablesResult = await pool.query(`
|
|
394
|
+
SELECT
|
|
395
|
+
c.relname as name,
|
|
396
|
+
n.nspname as schema,
|
|
397
|
+
s.srvname as server_name,
|
|
398
|
+
ft.ftoptions as options
|
|
399
|
+
FROM pg_foreign_table ft
|
|
400
|
+
JOIN pg_class c ON ft.ftrelid = c.oid
|
|
401
|
+
JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
402
|
+
JOIN pg_foreign_server s ON ft.ftserver = s.oid
|
|
403
|
+
WHERE n.nspname = 'public'
|
|
404
|
+
ORDER BY c.relname;
|
|
405
|
+
`);
|
|
406
|
+
const foreignTableNames = foreignTablesResult.rows.map(t => t.name);
|
|
407
|
+
let foreignTableColumns = new Map();
|
|
408
|
+
if (foreignTableNames.length > 0) {
|
|
409
|
+
const ftColsResult = await pool.query(`
|
|
410
|
+
SELECT
|
|
411
|
+
c.relname as table_name,
|
|
412
|
+
a.attname as column_name,
|
|
413
|
+
pg_catalog.format_type(a.atttypid, a.atttypmod) as data_type
|
|
414
|
+
FROM pg_attribute a
|
|
415
|
+
JOIN pg_class c ON a.attrelid = c.oid
|
|
416
|
+
JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
417
|
+
WHERE n.nspname = 'public'
|
|
418
|
+
AND c.relname = ANY($1)
|
|
419
|
+
AND a.attnum > 0
|
|
420
|
+
AND NOT a.attisdropped
|
|
421
|
+
ORDER BY c.relname, a.attnum;
|
|
422
|
+
`, [foreignTableNames]);
|
|
423
|
+
for (const row of ftColsResult.rows) {
|
|
424
|
+
const cols = foreignTableColumns.get(row.table_name) || [];
|
|
425
|
+
cols.push({ name: row.column_name, type: row.data_type });
|
|
426
|
+
foreignTableColumns.set(row.table_name, cols);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const foreignTables = foreignTablesResult.rows.map(t => ({
|
|
430
|
+
name: t.name,
|
|
431
|
+
schema: t.schema,
|
|
432
|
+
serverName: t.server_name,
|
|
433
|
+
columns: (foreignTableColumns.get(t.name) || []).map((c, i) => ({
|
|
434
|
+
name: c.name,
|
|
435
|
+
dataType: c.type,
|
|
436
|
+
isNullable: true,
|
|
437
|
+
defaultValue: null,
|
|
438
|
+
isPrimaryKey: false,
|
|
439
|
+
isUnique: false,
|
|
440
|
+
ordinalPosition: i + 1,
|
|
441
|
+
maxLength: null,
|
|
442
|
+
precision: null,
|
|
443
|
+
scale: null,
|
|
444
|
+
references: null,
|
|
445
|
+
})),
|
|
446
|
+
options: parseOptionsArray(t.options),
|
|
447
|
+
}));
|
|
343
448
|
onProgress?.('complete');
|
|
344
449
|
return {
|
|
345
450
|
tables,
|
|
@@ -347,12 +452,13 @@ export async function fastIntrospectDatabase(connection, onProgress, options) {
|
|
|
347
452
|
domains: [],
|
|
348
453
|
compositeTypes: [],
|
|
349
454
|
sequences: [],
|
|
455
|
+
collations,
|
|
350
456
|
functions,
|
|
351
457
|
triggers,
|
|
352
458
|
policies: [],
|
|
353
459
|
partitions,
|
|
354
|
-
foreignServers
|
|
355
|
-
foreignTables
|
|
460
|
+
foreignServers,
|
|
461
|
+
foreignTables,
|
|
356
462
|
extensions,
|
|
357
463
|
};
|
|
358
464
|
}
|
|
@@ -1,35 +1,8 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { isInitialized, getStagedChanges, getUnstagedChanges, getHead, loadCommit, } from "./repo-manager.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
red: (s) => isColorSupported ? `\x1b[31m${s}\x1b[0m` : s,
|
|
7
|
-
green: (s) => isColorSupported ? `\x1b[32m${s}\x1b[0m` : s,
|
|
8
|
-
yellow: (s) => isColorSupported ? `\x1b[33m${s}\x1b[0m` : s,
|
|
9
|
-
blue: (s) => isColorSupported ? `\x1b[34m${s}\x1b[0m` : s,
|
|
10
|
-
magenta: (s) => isColorSupported ? `\x1b[35m${s}\x1b[0m` : s,
|
|
11
|
-
cyan: (s) => isColorSupported ? `\x1b[36m${s}\x1b[0m` : s,
|
|
12
|
-
white: (s) => isColorSupported ? `\x1b[37m${s}\x1b[0m` : s,
|
|
13
|
-
gray: (s) => isColorSupported ? `\x1b[90m${s}\x1b[0m` : s,
|
|
14
|
-
bold: (s) => isColorSupported ? `\x1b[1m${s}\x1b[0m` : s,
|
|
15
|
-
dim: (s) => isColorSupported ? `\x1b[2m${s}\x1b[0m` : s,
|
|
16
|
-
};
|
|
17
|
-
export function error(message) {
|
|
18
|
-
console.error(`${colors.red('error:')} ${message}`);
|
|
19
|
-
}
|
|
20
|
-
export function fatal(message) {
|
|
21
|
-
console.error(`${colors.red('fatal:')} ${message}`);
|
|
22
|
-
process.exit(128);
|
|
23
|
-
}
|
|
24
|
-
export function warning(message) {
|
|
25
|
-
console.error(`${colors.yellow('warning:')} ${message}`);
|
|
26
|
-
}
|
|
27
|
-
export function hint(message) {
|
|
28
|
-
console.log(`${colors.yellow('hint:')} ${message}`);
|
|
29
|
-
}
|
|
30
|
-
export function success(message) {
|
|
31
|
-
console.log(`${colors.green('✓')} ${message}`);
|
|
32
|
-
}
|
|
4
|
+
export { colors, createSpinner, fatal, error, warning, hint, success, confirm, select, formatBytes, formatDuration, progressBar, requireInit, } from "./cli-utils.js";
|
|
5
|
+
import { colors, error, hint, fatal } from "./cli-utils.js";
|
|
33
6
|
export function info(message) {
|
|
34
7
|
console.log(message);
|
|
35
8
|
}
|
|
@@ -250,98 +223,3 @@ export function readSQLFile(filePath) {
|
|
|
250
223
|
const validation = validatePostgresSQL(content);
|
|
251
224
|
return { content, validation };
|
|
252
225
|
}
|
|
253
|
-
export function createSpinner() {
|
|
254
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
255
|
-
let frameIndex = 0;
|
|
256
|
-
let interval = null;
|
|
257
|
-
let currentMessage = '';
|
|
258
|
-
return {
|
|
259
|
-
start(message) {
|
|
260
|
-
currentMessage = message;
|
|
261
|
-
if (process.stdout.isTTY) {
|
|
262
|
-
interval = setInterval(() => {
|
|
263
|
-
process.stdout.write(`\r${frames[frameIndex]} ${currentMessage}`);
|
|
264
|
-
frameIndex = (frameIndex + 1) % frames.length;
|
|
265
|
-
}, 80);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
console.log(` ${message}...`);
|
|
269
|
-
}
|
|
270
|
-
},
|
|
271
|
-
stop() {
|
|
272
|
-
if (interval) {
|
|
273
|
-
clearInterval(interval);
|
|
274
|
-
interval = null;
|
|
275
|
-
if (process.stdout.isTTY) {
|
|
276
|
-
process.stdout.write('\r' + ' '.repeat(currentMessage.length + 4) + '\r');
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
succeed(message) {
|
|
281
|
-
this.stop();
|
|
282
|
-
console.log(`${colors.green('✓')} ${message}`);
|
|
283
|
-
},
|
|
284
|
-
fail(message) {
|
|
285
|
-
this.stop();
|
|
286
|
-
console.log(`${colors.red('✗')} ${message}`);
|
|
287
|
-
},
|
|
288
|
-
info(message) {
|
|
289
|
-
this.stop();
|
|
290
|
-
console.log(`${colors.blue('ℹ')} ${message}`);
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
import * as readline from 'readline';
|
|
295
|
-
export function confirm(question, defaultYes = true) {
|
|
296
|
-
const rl = readline.createInterface({
|
|
297
|
-
input: process.stdin,
|
|
298
|
-
output: process.stdout,
|
|
299
|
-
});
|
|
300
|
-
const suffix = defaultYes ? '[Y/n]' : '[y/N]';
|
|
301
|
-
return new Promise((resolve) => {
|
|
302
|
-
rl.question(`${question} ${colors.gray(suffix)} `, (answer) => {
|
|
303
|
-
rl.close();
|
|
304
|
-
const a = answer.trim().toLowerCase();
|
|
305
|
-
if (!a)
|
|
306
|
-
resolve(defaultYes);
|
|
307
|
-
else
|
|
308
|
-
resolve(a === 'y' || a === 'yes');
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
export function select(question, options) {
|
|
313
|
-
const rl = readline.createInterface({
|
|
314
|
-
input: process.stdin,
|
|
315
|
-
output: process.stdout,
|
|
316
|
-
});
|
|
317
|
-
console.log(question);
|
|
318
|
-
options.forEach((opt, i) => {
|
|
319
|
-
console.log(` ${i + 1}) ${opt}`);
|
|
320
|
-
});
|
|
321
|
-
return new Promise((resolve) => {
|
|
322
|
-
rl.question(`${colors.gray('Select [1-' + options.length + ']:')} `, (answer) => {
|
|
323
|
-
rl.close();
|
|
324
|
-
const num = parseInt(answer.trim(), 10);
|
|
325
|
-
if (num >= 1 && num <= options.length) {
|
|
326
|
-
resolve(num - 1);
|
|
327
|
-
}
|
|
328
|
-
else {
|
|
329
|
-
resolve(0);
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
export function formatBytes(bytes) {
|
|
335
|
-
if (bytes < 1024)
|
|
336
|
-
return `${bytes} B`;
|
|
337
|
-
if (bytes < 1024 * 1024)
|
|
338
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
339
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
340
|
-
}
|
|
341
|
-
export function formatDuration(ms) {
|
|
342
|
-
if (ms < 1000)
|
|
343
|
-
return `${ms}ms`;
|
|
344
|
-
if (ms < 60000)
|
|
345
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
346
|
-
return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
|
|
347
|
-
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const pools = new Map();
|
|
2
|
+
const IDLE_TIMEOUT = 30000;
|
|
3
|
+
let cleanupInterval = null;
|
|
4
|
+
function getPoolKey(config) {
|
|
5
|
+
if (config.url) {
|
|
6
|
+
return config.url;
|
|
7
|
+
}
|
|
8
|
+
return `${config.host || 'localhost'}:${config.port || 5432}/${config.database}@${config.user}`;
|
|
9
|
+
}
|
|
10
|
+
function startCleanupInterval() {
|
|
11
|
+
if (cleanupInterval)
|
|
12
|
+
return;
|
|
13
|
+
cleanupInterval = setInterval(() => {
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
for (const [key, entry] of pools.entries()) {
|
|
16
|
+
if (entry.refCount === 0 && now - entry.lastUsed > IDLE_TIMEOUT) {
|
|
17
|
+
entry.pool.end().catch(() => { });
|
|
18
|
+
pools.delete(key);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (pools.size === 0 && cleanupInterval) {
|
|
22
|
+
clearInterval(cleanupInterval);
|
|
23
|
+
cleanupInterval = null;
|
|
24
|
+
}
|
|
25
|
+
}, IDLE_TIMEOUT);
|
|
26
|
+
cleanupInterval.unref();
|
|
27
|
+
}
|
|
28
|
+
export async function getPool(config) {
|
|
29
|
+
const key = getPoolKey(config);
|
|
30
|
+
let entry = pools.get(key);
|
|
31
|
+
if (entry) {
|
|
32
|
+
entry.refCount++;
|
|
33
|
+
entry.lastUsed = Date.now();
|
|
34
|
+
return entry.pool;
|
|
35
|
+
}
|
|
36
|
+
const { Pool } = await import("../../addon/pg/index.js");
|
|
37
|
+
const pool = new Pool({
|
|
38
|
+
host: config.host,
|
|
39
|
+
port: config.port || 5432,
|
|
40
|
+
database: config.database,
|
|
41
|
+
user: config.user,
|
|
42
|
+
password: config.password,
|
|
43
|
+
connectionString: config.url,
|
|
44
|
+
ssl: config.ssl,
|
|
45
|
+
max: config.max || 10,
|
|
46
|
+
idleTimeoutMillis: config.idleTimeoutMillis || 10000,
|
|
47
|
+
connectionTimeoutMillis: config.connectionTimeoutMillis || 5000,
|
|
48
|
+
});
|
|
49
|
+
entry = {
|
|
50
|
+
pool,
|
|
51
|
+
refCount: 1,
|
|
52
|
+
lastUsed: Date.now(),
|
|
53
|
+
};
|
|
54
|
+
pools.set(key, entry);
|
|
55
|
+
startCleanupInterval();
|
|
56
|
+
return pool;
|
|
57
|
+
}
|
|
58
|
+
export function releasePool(config) {
|
|
59
|
+
const key = getPoolKey(config);
|
|
60
|
+
const entry = pools.get(key);
|
|
61
|
+
if (entry && entry.refCount > 0) {
|
|
62
|
+
entry.refCount--;
|
|
63
|
+
entry.lastUsed = Date.now();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export async function withPool(config, fn) {
|
|
67
|
+
const pool = await getPool(config);
|
|
68
|
+
try {
|
|
69
|
+
return await fn(pool);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
releasePool(config);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export async function withClient(config, fn) {
|
|
76
|
+
const pool = await getPool(config);
|
|
77
|
+
const client = await pool.connect();
|
|
78
|
+
try {
|
|
79
|
+
return await fn(client);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
client.release();
|
|
83
|
+
releasePool(config);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export async function withTransaction(config, fn) {
|
|
87
|
+
return withClient(config, async (client) => {
|
|
88
|
+
await client.query('BEGIN');
|
|
89
|
+
try {
|
|
90
|
+
const result = await fn(client);
|
|
91
|
+
await client.query('COMMIT');
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
await client.query('ROLLBACK');
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export async function closeAllPools() {
|
|
101
|
+
if (cleanupInterval) {
|
|
102
|
+
clearInterval(cleanupInterval);
|
|
103
|
+
cleanupInterval = null;
|
|
104
|
+
}
|
|
105
|
+
const closePromises = [];
|
|
106
|
+
for (const [key, entry] of pools.entries()) {
|
|
107
|
+
closePromises.push(entry.pool.end());
|
|
108
|
+
pools.delete(key);
|
|
109
|
+
}
|
|
110
|
+
await Promise.all(closePromises);
|
|
111
|
+
}
|
|
112
|
+
export function getActivePoolCount() {
|
|
113
|
+
return pools.size;
|
|
114
|
+
}
|
|
@@ -1,19 +1,69 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
const CONFIG_FILENAMES = [
|
|
5
|
+
'relq.config.ts',
|
|
6
|
+
'relq.config.js',
|
|
7
|
+
'relq.config.mjs',
|
|
8
|
+
];
|
|
9
|
+
function hasProjectMarker(dir) {
|
|
10
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
11
|
+
if (fs.existsSync(path.join(dir, filename))) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
3
20
|
export function findProjectRoot(startDir = process.cwd()) {
|
|
4
21
|
let currentDir = path.resolve(startDir);
|
|
5
22
|
const root = path.parse(currentDir).root;
|
|
23
|
+
const homeDir = os.homedir();
|
|
6
24
|
while (currentDir !== root) {
|
|
7
|
-
|
|
8
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
25
|
+
if (hasProjectMarker(currentDir)) {
|
|
9
26
|
return currentDir;
|
|
10
27
|
}
|
|
28
|
+
if (currentDir === homeDir) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
11
31
|
currentDir = path.dirname(currentDir);
|
|
12
32
|
}
|
|
13
|
-
return
|
|
33
|
+
return null;
|
|
14
34
|
}
|
|
15
35
|
export function getRelqDir(startDir = process.cwd()) {
|
|
16
|
-
const projectRoot = findProjectRoot(startDir)
|
|
36
|
+
const projectRoot = findProjectRoot(startDir);
|
|
37
|
+
if (!projectRoot) {
|
|
38
|
+
const colors = {
|
|
39
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
40
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
41
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
42
|
+
};
|
|
43
|
+
console.error('');
|
|
44
|
+
console.error(colors.red('fatal:') + ' not a relq project (or any of the parent directories)');
|
|
45
|
+
console.error('');
|
|
46
|
+
console.error(colors.yellow('hint:') + ` Run ${colors.cyan('relq init')} in your project directory to initialize relq.`);
|
|
47
|
+
console.error('');
|
|
48
|
+
process.exit(128);
|
|
49
|
+
}
|
|
17
50
|
return path.join(projectRoot, '.relq');
|
|
18
51
|
}
|
|
19
|
-
export
|
|
52
|
+
export function getProjectRoot(startDir = process.cwd()) {
|
|
53
|
+
const projectRoot = findProjectRoot(startDir);
|
|
54
|
+
if (!projectRoot) {
|
|
55
|
+
const colors = {
|
|
56
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
57
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
58
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
59
|
+
};
|
|
60
|
+
console.error('');
|
|
61
|
+
console.error(colors.red('fatal:') + ' not a relq project (or any of the parent directories)');
|
|
62
|
+
console.error('');
|
|
63
|
+
console.error(colors.yellow('hint:') + ` Run ${colors.cyan('relq init')} in your project directory to initialize relq.`);
|
|
64
|
+
console.error('');
|
|
65
|
+
process.exit(128);
|
|
66
|
+
}
|
|
67
|
+
return projectRoot;
|
|
68
|
+
}
|
|
69
|
+
export default { findProjectRoot, getRelqDir, getProjectRoot };
|
|
@@ -8,6 +8,7 @@ const STAGED_FILE = 'staged.json';
|
|
|
8
8
|
const WORKING_FILE = 'working.json';
|
|
9
9
|
const SNAPSHOT_FILE = 'snapshot.json';
|
|
10
10
|
const COMMITS_DIR = 'commits';
|
|
11
|
+
const FILE_HASH_FILE = 'file_hash';
|
|
11
12
|
export function isInitialized(projectRoot = process.cwd()) {
|
|
12
13
|
const relqPath = path.join(projectRoot, RELQ_DIR);
|
|
13
14
|
const headPath = path.join(relqPath, HEAD_FILE);
|
|
@@ -172,6 +173,41 @@ export function saveSnapshot(schema, projectRoot = process.cwd()) {
|
|
|
172
173
|
const snapshotPath = path.join(projectRoot, RELQ_DIR, SNAPSHOT_FILE);
|
|
173
174
|
fs.writeFileSync(snapshotPath, JSON.stringify(schema, null, 2), 'utf-8');
|
|
174
175
|
}
|
|
176
|
+
export function hashFileContent(content) {
|
|
177
|
+
return crypto.createHash('sha1').update(content, 'utf8').digest('hex');
|
|
178
|
+
}
|
|
179
|
+
export function getSavedFileHash(projectRoot = process.cwd()) {
|
|
180
|
+
const hashPath = path.join(projectRoot, RELQ_DIR, FILE_HASH_FILE);
|
|
181
|
+
if (!fs.existsSync(hashPath)) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return fs.readFileSync(hashPath, 'utf-8').trim() || null;
|
|
185
|
+
}
|
|
186
|
+
export function saveFileHash(hash, projectRoot = process.cwd()) {
|
|
187
|
+
const hashPath = path.join(projectRoot, RELQ_DIR, FILE_HASH_FILE);
|
|
188
|
+
fs.writeFileSync(hashPath, hash, 'utf-8');
|
|
189
|
+
}
|
|
190
|
+
export function detectFileChanges(schemaPath, projectRoot = process.cwd()) {
|
|
191
|
+
if (!fs.existsSync(schemaPath)) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const currentContent = fs.readFileSync(schemaPath, 'utf-8');
|
|
195
|
+
const currentHash = hashFileContent(currentContent);
|
|
196
|
+
const savedHash = getSavedFileHash(projectRoot);
|
|
197
|
+
if (!savedHash || currentHash === savedHash) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
id: `file_${currentHash.substring(0, 8)}`,
|
|
202
|
+
type: 'ALTER',
|
|
203
|
+
objectType: 'SCHEMA_FILE',
|
|
204
|
+
objectName: path.basename(schemaPath),
|
|
205
|
+
before: { hash: savedHash },
|
|
206
|
+
after: { hash: currentHash },
|
|
207
|
+
sql: '-- Schema file modified (comments, formatting, or manual edits)',
|
|
208
|
+
detectedAt: new Date().toISOString(),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
175
211
|
export function loadWorkingState(projectRoot = process.cwd()) {
|
|
176
212
|
const workingPath = path.join(projectRoot, RELQ_DIR, WORKING_FILE);
|
|
177
213
|
if (!fs.existsSync(workingPath)) {
|
|
@@ -206,6 +242,12 @@ export function addUnstagedChanges(changes, projectRoot = process.cwd()) {
|
|
|
206
242
|
state.timestamp = new Date().toISOString();
|
|
207
243
|
saveWorkingState(state, projectRoot);
|
|
208
244
|
}
|
|
245
|
+
export function clearUnstagedChanges(projectRoot = process.cwd()) {
|
|
246
|
+
const state = getOrCreateWorkingState(projectRoot);
|
|
247
|
+
state.unstaged = [];
|
|
248
|
+
state.timestamp = new Date().toISOString();
|
|
249
|
+
saveWorkingState(state, projectRoot);
|
|
250
|
+
}
|
|
209
251
|
export function stageChanges(patterns, projectRoot = process.cwd()) {
|
|
210
252
|
const state = getOrCreateWorkingState(projectRoot);
|
|
211
253
|
const staged = [];
|