cynx-lib 1.3.50 → 1.3.51

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cynx-lib",
3
- "version": "1.3.50",
3
+ "version": "1.3.51",
4
4
  "type": "module",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
@@ -196,6 +196,7 @@ async function sync_db(options = {}) {
196
196
  freshCheckTable,
197
197
  logger: rawLogger = console,
198
198
  dryRun = false,
199
+ syncColumnTypes = false,
199
200
  maxRetries = 3,
200
201
  retryDelayMs = 300,
201
202
  throwOnError = false
@@ -316,16 +317,21 @@ async function sync_db(options = {}) {
316
317
  log("Analyzing schema.sql and comparing with database catalogs (Zero-Lock)...");
317
318
  const [tablesRes, colsRes, idxRes, conRes] = await Promise.all([
318
319
  queryFn("SELECT table_name FROM information_schema.tables WHERE table_schema = $1 AND table_type = 'BASE TABLE'", [tableSchema]),
319
- queryFn("SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = $1", [tableSchema]),
320
+ queryFn("SELECT table_name, column_name, data_type, udt_name FROM information_schema.columns WHERE table_schema = $1", [tableSchema]),
320
321
  queryFn("SELECT indexname FROM pg_indexes WHERE schemaname = $1", [tableSchema]),
321
322
  queryFn("SELECT conname FROM pg_constraint")
322
323
  ]);
323
324
  const existingTables = new Set((tablesRes.rows || []).map((r) => String(r.table_name).toLowerCase()));
324
325
  const existingColumns = new Set((colsRes.rows || []).map((r) => `${String(r.table_name).toLowerCase()}.${String(r.column_name).toLowerCase()}`));
326
+ const existingColumnTypes = new Map((colsRes.rows || []).map((r) => [
327
+ `${String(r.table_name).toLowerCase()}.${String(r.column_name).toLowerCase()}`,
328
+ { dataType: String(r.data_type || "").toLowerCase(), udtName: String(r.udt_name || "").toLowerCase() }
329
+ ]));
325
330
  const existingIndexes = new Set((idxRes.rows || []).map((r) => String(r.indexname).toLowerCase()));
326
331
  const existingConstraints = new Set((conRes.rows || []).map((r) => String(r.conname).toLowerCase()));
327
332
  const createdTables = [];
328
333
  const addedColumns = [];
334
+ const alteredColumns = [];
329
335
  const createdIndexes = [];
330
336
  const droppedConstraints = [];
331
337
  const droppedIndexes = [];
@@ -373,6 +379,36 @@ async function sync_db(options = {}) {
373
379
  addedColumns.push(colKey);
374
380
  log(`[DryRun] Would add column: ${colKey}`);
375
381
  }
382
+ } else if (syncColumnTypes) {
383
+ const currentType = existingColumnTypes.get(colKey);
384
+ const defUpper = col.def.toUpperCase().trim();
385
+ let targetType = null;
386
+ if (/\bTIMESTAMPTZ\b|\bTIMESTAMP\s+WITH\s+TIME\s+ZONE\b/.test(defUpper)) {
387
+ if (currentType && (currentType.dataType === "timestamp without time zone" || currentType.udtName === "timestamp")) {
388
+ targetType = "TIMESTAMPTZ";
389
+ }
390
+ } else if (/\bBIGINT\b|\bBIGSERIAL\b/.test(defUpper)) {
391
+ if (currentType && (currentType.dataType === "integer" || currentType.udtName === "int4")) {
392
+ targetType = "BIGINT";
393
+ }
394
+ } else if (/\bTEXT\b/.test(defUpper)) {
395
+ if (currentType && (currentType.dataType === "character varying" || currentType.udtName === "varchar")) {
396
+ targetType = "TEXT";
397
+ }
398
+ }
399
+ if (targetType) {
400
+ const sql = `ALTER TABLE ${table.name} ALTER COLUMN ${col.name} TYPE ${targetType};`;
401
+ if (!dryRun) {
402
+ const ok = await runSafeDdl(queryFn, sql, `alter column ${colKey} to ${targetType}`, safeDdlOpts);
403
+ if (ok) {
404
+ alteredColumns.push(`${colKey} (${targetType})`);
405
+ log(`Altered column type: ${colKey} -> ${targetType}`);
406
+ }
407
+ } else {
408
+ alteredColumns.push(`${colKey} (${targetType})`);
409
+ log(`[DryRun] Would alter column type: ${colKey} -> ${targetType}`);
410
+ }
411
+ }
376
412
  }
377
413
  }
378
414
  }
@@ -445,11 +481,12 @@ async function sync_db(options = {}) {
445
481
  const summaryParts = [];
446
482
  if (createdTables.length > 0) summaryParts.push(`${createdTables.length} tables created`);
447
483
  if (addedColumns.length > 0) summaryParts.push(`${addedColumns.length} columns added`);
484
+ if (alteredColumns.length > 0) summaryParts.push(`${alteredColumns.length} columns altered`);
448
485
  if (createdIndexes.length > 0) summaryParts.push(`${createdIndexes.length} indexes created`);
449
486
  if (droppedConstraints.length > 0) summaryParts.push(`${droppedConstraints.length} constraints dropped`);
450
487
  if (droppedIndexes.length > 0) summaryParts.push(`${droppedIndexes.length} indexes dropped`);
451
488
  if (executedDoBlocks > 0) summaryParts.push(`${executedDoBlocks} migration blocks run`);
452
- const changesCount = createdTables.length + addedColumns.length + createdIndexes.length + droppedConstraints.length + droppedIndexes.length;
489
+ const changesCount = createdTables.length + addedColumns.length + alteredColumns.length + createdIndexes.length + droppedConstraints.length + droppedIndexes.length;
453
490
  const summary = summaryParts.length > 0 ? `Database synced in ${durationMs}ms: ${summaryParts.join(", ")}` : `Database schema is up to date (0 changes needed, ${durationMs}ms)`;
454
491
  log(summary);
455
492
  return {
@@ -457,6 +494,7 @@ async function sync_db(options = {}) {
457
494
  isFreshDb: false,
458
495
  createdTables,
459
496
  addedColumns,
497
+ alteredColumns,
460
498
  createdIndexes,
461
499
  droppedConstraints,
462
500
  droppedIndexes,
package/utils/utils/db.js CHANGED
@@ -281,6 +281,7 @@ export async function sync_db(options = {}) {
281
281
  freshCheckTable,
282
282
  logger: rawLogger = console,
283
283
  dryRun = false,
284
+ syncColumnTypes = false,
284
285
  maxRetries = 3,
285
286
  retryDelayMs = 300,
286
287
  throwOnError = false,
@@ -409,18 +410,23 @@ export async function sync_db(options = {}) {
409
410
  // ── 2. Query System Catalogs in parallel (AccessShareLock only - 0ms blocking) ──
410
411
  const [tablesRes, colsRes, idxRes, conRes] = await Promise.all([
411
412
  queryFn("SELECT table_name FROM information_schema.tables WHERE table_schema = $1 AND table_type = 'BASE TABLE'", [tableSchema]),
412
- queryFn("SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = $1", [tableSchema]),
413
+ queryFn("SELECT table_name, column_name, data_type, udt_name FROM information_schema.columns WHERE table_schema = $1", [tableSchema]),
413
414
  queryFn("SELECT indexname FROM pg_indexes WHERE schemaname = $1", [tableSchema]),
414
415
  queryFn("SELECT conname FROM pg_constraint"),
415
416
  ]);
416
417
 
417
418
  const existingTables = new Set((tablesRes.rows || []).map((r) => String(r.table_name).toLowerCase()));
418
419
  const existingColumns = new Set((colsRes.rows || []).map((r) => `${String(r.table_name).toLowerCase()}.${String(r.column_name).toLowerCase()}`));
420
+ const existingColumnTypes = new Map((colsRes.rows || []).map((r) => [
421
+ `${String(r.table_name).toLowerCase()}.${String(r.column_name).toLowerCase()}`,
422
+ { dataType: String(r.data_type || '').toLowerCase(), udtName: String(r.udt_name || '').toLowerCase() }
423
+ ]));
419
424
  const existingIndexes = new Set((idxRes.rows || []).map((r) => String(r.indexname).toLowerCase()));
420
425
  const existingConstraints = new Set((conRes.rows || []).map((r) => String(r.conname).toLowerCase()));
421
426
 
422
427
  const createdTables = [];
423
428
  const addedColumns = [];
429
+ const alteredColumns = [];
424
430
  const createdIndexes = [];
425
431
  const droppedConstraints = [];
426
432
  const droppedIndexes = [];
@@ -476,6 +482,39 @@ export async function sync_db(options = {}) {
476
482
  addedColumns.push(colKey);
477
483
  log(`[DryRun] Would add column: ${colKey}`);
478
484
  }
485
+ } else if (syncColumnTypes) {
486
+ // Check for safe column type discrepancies when syncColumnTypes flag is true
487
+ const currentType = existingColumnTypes.get(colKey);
488
+ const defUpper = col.def.toUpperCase().trim();
489
+
490
+ let targetType = null;
491
+ if (/\bTIMESTAMPTZ\b|\bTIMESTAMP\s+WITH\s+TIME\s+ZONE\b/.test(defUpper)) {
492
+ if (currentType && (currentType.dataType === 'timestamp without time zone' || currentType.udtName === 'timestamp')) {
493
+ targetType = 'TIMESTAMPTZ';
494
+ }
495
+ } else if (/\bBIGINT\b|\bBIGSERIAL\b/.test(defUpper)) {
496
+ if (currentType && (currentType.dataType === 'integer' || currentType.udtName === 'int4')) {
497
+ targetType = 'BIGINT';
498
+ }
499
+ } else if (/\bTEXT\b/.test(defUpper)) {
500
+ if (currentType && (currentType.dataType === 'character varying' || currentType.udtName === 'varchar')) {
501
+ targetType = 'TEXT';
502
+ }
503
+ }
504
+
505
+ if (targetType) {
506
+ const sql = `ALTER TABLE ${table.name} ALTER COLUMN ${col.name} TYPE ${targetType};`;
507
+ if (!dryRun) {
508
+ const ok = await runSafeDdl(queryFn, sql, `alter column ${colKey} to ${targetType}`, safeDdlOpts);
509
+ if (ok) {
510
+ alteredColumns.push(`${colKey} (${targetType})`);
511
+ log(`Altered column type: ${colKey} -> ${targetType}`);
512
+ }
513
+ } else {
514
+ alteredColumns.push(`${colKey} (${targetType})`);
515
+ log(`[DryRun] Would alter column type: ${colKey} -> ${targetType}`);
516
+ }
517
+ }
479
518
  }
480
519
  }
481
520
  }
@@ -556,12 +595,13 @@ export async function sync_db(options = {}) {
556
595
  const summaryParts = [];
557
596
  if (createdTables.length > 0) summaryParts.push(`${createdTables.length} tables created`);
558
597
  if (addedColumns.length > 0) summaryParts.push(`${addedColumns.length} columns added`);
598
+ if (alteredColumns.length > 0) summaryParts.push(`${alteredColumns.length} columns altered`);
559
599
  if (createdIndexes.length > 0) summaryParts.push(`${createdIndexes.length} indexes created`);
560
600
  if (droppedConstraints.length > 0) summaryParts.push(`${droppedConstraints.length} constraints dropped`);
561
601
  if (droppedIndexes.length > 0) summaryParts.push(`${droppedIndexes.length} indexes dropped`);
562
602
  if (executedDoBlocks > 0) summaryParts.push(`${executedDoBlocks} migration blocks run`);
563
603
 
564
- const changesCount = createdTables.length + addedColumns.length + createdIndexes.length + droppedConstraints.length + droppedIndexes.length;
604
+ const changesCount = createdTables.length + addedColumns.length + alteredColumns.length + createdIndexes.length + droppedConstraints.length + droppedIndexes.length;
565
605
  const summary = summaryParts.length > 0
566
606
  ? `Database synced in ${durationMs}ms: ${summaryParts.join(', ')}`
567
607
  : `Database schema is up to date (0 changes needed, ${durationMs}ms)`;
@@ -573,6 +613,7 @@ export async function sync_db(options = {}) {
573
613
  isFreshDb: false,
574
614
  createdTables,
575
615
  addedColumns,
616
+ alteredColumns,
576
617
  createdIndexes,
577
618
  droppedConstraints,
578
619
  droppedIndexes,