relq 1.0.91 → 1.0.92

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.
@@ -345,16 +345,6 @@ function diffIndexes(local, remote) {
345
345
  }
346
346
  function diffConstraints(local, remote) {
347
347
  const diffs = [];
348
- const sig = (c) => {
349
- const def = (c.definition || '').replace(/"/g, '').replace(/\s+/g, ' ').trim().toLowerCase();
350
- return `${c.type}:${def}`;
351
- };
352
- const localSigs = new Map();
353
- const remoteSigs = new Map();
354
- for (const c of local)
355
- localSigs.set(sig(c), c);
356
- for (const c of remote)
357
- remoteSigs.set(sig(c), c);
358
348
  const toConstraintInfo = (c) => ({
359
349
  name: c.name,
360
350
  type: (c.type || 'CHECK'),
@@ -362,13 +352,111 @@ function diffConstraints(local, remote) {
362
352
  definition: c.definition || '',
363
353
  trackingId: c.trackingId,
364
354
  });
365
- for (const [s, c] of remoteSigs) {
366
- if (!localSigs.has(s)) {
355
+ const matchedLocal = new Set();
356
+ const matchedRemote = new Set();
357
+ for (let ri = 0; ri < remote.length; ri++) {
358
+ if (matchedRemote.has(ri))
359
+ continue;
360
+ const rc = remote[ri];
361
+ if (!rc.trackingId)
362
+ continue;
363
+ for (let li = 0; li < local.length; li++) {
364
+ if (matchedLocal.has(li))
365
+ continue;
366
+ const lc = local[li];
367
+ if (lc.trackingId && lc.trackingId === rc.trackingId) {
368
+ matchedLocal.add(li);
369
+ matchedRemote.add(ri);
370
+ break;
371
+ }
372
+ }
373
+ }
374
+ for (let ri = 0; ri < remote.length; ri++) {
375
+ if (matchedRemote.has(ri))
376
+ continue;
377
+ const rc = remote[ri];
378
+ if (!rc.name)
379
+ continue;
380
+ for (let li = 0; li < local.length; li++) {
381
+ if (matchedLocal.has(li))
382
+ continue;
383
+ const lc = local[li];
384
+ if (lc.name && lc.name === rc.name) {
385
+ matchedLocal.add(li);
386
+ matchedRemote.add(ri);
387
+ break;
388
+ }
389
+ }
390
+ }
391
+ const semanticSig = (c) => {
392
+ const def = c.definition || '';
393
+ const type = (c.type || '').toUpperCase();
394
+ if (type === 'PRIMARY KEY' || type === 'UNIQUE') {
395
+ const colMatch = def.match(/\(([^)]+)\)/);
396
+ if (colMatch) {
397
+ const cols = colMatch[1].replace(/"/g, '').split(',').map(s => s.trim().toLowerCase()).sort().join(',');
398
+ return `${type}:${cols}`;
399
+ }
400
+ }
401
+ if (type === 'FOREIGN KEY') {
402
+ const fkMatch = def.match(/FOREIGN\s+KEY\s*\(([^)]+)\)\s*REFERENCES\s+"?(\w+)"?/i);
403
+ if (fkMatch) {
404
+ const srcCols = fkMatch[1].replace(/"/g, '').split(',').map(s => s.trim().toLowerCase()).sort().join(',');
405
+ const targetTable = fkMatch[2].toLowerCase();
406
+ return `FK:${srcCols}→${targetTable}`;
407
+ }
408
+ }
409
+ return null;
410
+ };
411
+ for (let ri = 0; ri < remote.length; ri++) {
412
+ if (matchedRemote.has(ri))
413
+ continue;
414
+ const rSig = semanticSig(remote[ri]);
415
+ if (!rSig)
416
+ continue;
417
+ for (let li = 0; li < local.length; li++) {
418
+ if (matchedLocal.has(li))
419
+ continue;
420
+ const lSig = semanticSig(local[li]);
421
+ if (lSig && lSig === rSig) {
422
+ matchedLocal.add(li);
423
+ matchedRemote.add(ri);
424
+ break;
425
+ }
426
+ }
427
+ }
428
+ const unmatchedLocalChecks = local
429
+ .map((c, i) => ({ c, i }))
430
+ .filter(({ c, i }) => !matchedLocal.has(i) && (c.type || '').toUpperCase() === 'CHECK');
431
+ const unmatchedRemoteChecks = remote
432
+ .map((c, i) => ({ c, i }))
433
+ .filter(({ c, i }) => !matchedRemote.has(i) && (c.type || '').toUpperCase() === 'CHECK');
434
+ for (const { c: rc, i: ri } of unmatchedRemoteChecks) {
435
+ if (matchedRemote.has(ri))
436
+ continue;
437
+ if (!rc.name)
438
+ continue;
439
+ for (const { c: lc, i: li } of unmatchedLocalChecks) {
440
+ if (matchedLocal.has(li))
441
+ continue;
442
+ if (lc.name && (lc.name === rc.name ||
443
+ (lc.name.endsWith('_check') && rc.name.endsWith('_check') &&
444
+ lc.name.split('_').slice(-2).join('_') === rc.name.split('_').slice(-2).join('_')))) {
445
+ matchedLocal.add(li);
446
+ matchedRemote.add(ri);
447
+ break;
448
+ }
449
+ }
450
+ }
451
+ for (let ri = 0; ri < remote.length; ri++) {
452
+ if (!matchedRemote.has(ri)) {
453
+ const c = remote[ri];
367
454
  diffs.push({ name: c.name || c.definition || 'unnamed', type: 'added', after: toConstraintInfo(c) });
368
455
  }
369
456
  }
370
- for (const [s, c] of localSigs) {
371
- if (!remoteSigs.has(s)) {
457
+ for (let li = 0; li < local.length; li++) {
458
+ if (!matchedLocal.has(li)) {
459
+ const c = local[li];
372
460
  diffs.push({ name: c.name || c.definition || 'unnamed', type: 'removed', before: toConstraintInfo(c) });
373
461
  }
374
462
  }
@@ -542,32 +630,43 @@ function formatCategorizedSummary(diff) {
542
630
  idxRemoved++;
543
631
  }
544
632
  for (const con of table.constraints || []) {
545
- const name = (con.name || '').toLowerCase();
546
- const isFk = name.includes('fkey') || name.includes('foreign');
547
- const isUq = name.includes('unique') || name.includes('_key');
548
- const isPk = name.includes('pkey') || name.includes('primary');
549
633
  const before = con.before;
550
634
  const after = con.after;
551
635
  const conType = (before?.type || after?.type || '').toUpperCase();
552
- if (conType === 'FOREIGN KEY' || isFk) {
636
+ let category = 'OTHER';
637
+ if (conType) {
638
+ category = conType;
639
+ }
640
+ else {
641
+ const name = (con.name || '').toLowerCase();
642
+ if (name.includes('pkey') || name.includes('primary'))
643
+ category = 'PRIMARY KEY';
644
+ else if (name.includes('fkey') || name.includes('foreign'))
645
+ category = 'FOREIGN KEY';
646
+ else if (name.includes('_check'))
647
+ category = 'CHECK';
648
+ else if (name.includes('_key') || name.includes('unique'))
649
+ category = 'UNIQUE';
650
+ }
651
+ if (category === 'FOREIGN KEY') {
553
652
  if (con.type === 'added')
554
653
  fkAdded++;
555
654
  else if (con.type === 'removed')
556
655
  fkRemoved++;
557
656
  }
558
- else if (conType === 'UNIQUE' || isUq) {
657
+ else if (category === 'UNIQUE') {
559
658
  if (con.type === 'added')
560
659
  uniqueAdded++;
561
660
  else if (con.type === 'removed')
562
661
  uniqueRemoved++;
563
662
  }
564
- else if (conType === 'PRIMARY KEY' || isPk) {
663
+ else if (category === 'PRIMARY KEY') {
565
664
  if (con.type === 'added')
566
665
  pkAdded++;
567
666
  else if (con.type === 'removed')
568
667
  pkRemoved++;
569
668
  }
570
- else if (conType === 'CHECK') {
669
+ else if (category === 'CHECK') {
571
670
  if (con.type === 'added')
572
671
  checkAdded++;
573
672
  else if (con.type === 'removed')
@@ -419,6 +419,18 @@ function tableToAST(table) {
419
419
  col.isUnique = true;
420
420
  }
421
421
  }
422
+ const hasPKConstraint = constraints.some(c => c.type === 'PRIMARY KEY');
423
+ if (!hasPKConstraint) {
424
+ const pkCols = columns.filter(c => c.isPrimaryKey).map(c => c.name);
425
+ if (pkCols.length > 0) {
426
+ constraints.push({
427
+ name: `${table.$name}_pkey`,
428
+ type: 'PRIMARY KEY',
429
+ columns: pkCols,
430
+ trackingId: `${table.$name}_pkey`,
431
+ });
432
+ }
433
+ }
422
434
  return {
423
435
  name: table.$name,
424
436
  schema: table.$schema || 'public',
@@ -334,16 +334,6 @@ function diffIndexes(local, remote) {
334
334
  }
335
335
  function diffConstraints(local, remote) {
336
336
  const diffs = [];
337
- const sig = (c) => {
338
- const def = (c.definition || '').replace(/"/g, '').replace(/\s+/g, ' ').trim().toLowerCase();
339
- return `${c.type}:${def}`;
340
- };
341
- const localSigs = new Map();
342
- const remoteSigs = new Map();
343
- for (const c of local)
344
- localSigs.set(sig(c), c);
345
- for (const c of remote)
346
- remoteSigs.set(sig(c), c);
347
337
  const toConstraintInfo = (c) => ({
348
338
  name: c.name,
349
339
  type: (c.type || 'CHECK'),
@@ -351,13 +341,111 @@ function diffConstraints(local, remote) {
351
341
  definition: c.definition || '',
352
342
  trackingId: c.trackingId,
353
343
  });
354
- for (const [s, c] of remoteSigs) {
355
- if (!localSigs.has(s)) {
344
+ const matchedLocal = new Set();
345
+ const matchedRemote = new Set();
346
+ for (let ri = 0; ri < remote.length; ri++) {
347
+ if (matchedRemote.has(ri))
348
+ continue;
349
+ const rc = remote[ri];
350
+ if (!rc.trackingId)
351
+ continue;
352
+ for (let li = 0; li < local.length; li++) {
353
+ if (matchedLocal.has(li))
354
+ continue;
355
+ const lc = local[li];
356
+ if (lc.trackingId && lc.trackingId === rc.trackingId) {
357
+ matchedLocal.add(li);
358
+ matchedRemote.add(ri);
359
+ break;
360
+ }
361
+ }
362
+ }
363
+ for (let ri = 0; ri < remote.length; ri++) {
364
+ if (matchedRemote.has(ri))
365
+ continue;
366
+ const rc = remote[ri];
367
+ if (!rc.name)
368
+ continue;
369
+ for (let li = 0; li < local.length; li++) {
370
+ if (matchedLocal.has(li))
371
+ continue;
372
+ const lc = local[li];
373
+ if (lc.name && lc.name === rc.name) {
374
+ matchedLocal.add(li);
375
+ matchedRemote.add(ri);
376
+ break;
377
+ }
378
+ }
379
+ }
380
+ const semanticSig = (c) => {
381
+ const def = c.definition || '';
382
+ const type = (c.type || '').toUpperCase();
383
+ if (type === 'PRIMARY KEY' || type === 'UNIQUE') {
384
+ const colMatch = def.match(/\(([^)]+)\)/);
385
+ if (colMatch) {
386
+ const cols = colMatch[1].replace(/"/g, '').split(',').map(s => s.trim().toLowerCase()).sort().join(',');
387
+ return `${type}:${cols}`;
388
+ }
389
+ }
390
+ if (type === 'FOREIGN KEY') {
391
+ const fkMatch = def.match(/FOREIGN\s+KEY\s*\(([^)]+)\)\s*REFERENCES\s+"?(\w+)"?/i);
392
+ if (fkMatch) {
393
+ const srcCols = fkMatch[1].replace(/"/g, '').split(',').map(s => s.trim().toLowerCase()).sort().join(',');
394
+ const targetTable = fkMatch[2].toLowerCase();
395
+ return `FK:${srcCols}→${targetTable}`;
396
+ }
397
+ }
398
+ return null;
399
+ };
400
+ for (let ri = 0; ri < remote.length; ri++) {
401
+ if (matchedRemote.has(ri))
402
+ continue;
403
+ const rSig = semanticSig(remote[ri]);
404
+ if (!rSig)
405
+ continue;
406
+ for (let li = 0; li < local.length; li++) {
407
+ if (matchedLocal.has(li))
408
+ continue;
409
+ const lSig = semanticSig(local[li]);
410
+ if (lSig && lSig === rSig) {
411
+ matchedLocal.add(li);
412
+ matchedRemote.add(ri);
413
+ break;
414
+ }
415
+ }
416
+ }
417
+ const unmatchedLocalChecks = local
418
+ .map((c, i) => ({ c, i }))
419
+ .filter(({ c, i }) => !matchedLocal.has(i) && (c.type || '').toUpperCase() === 'CHECK');
420
+ const unmatchedRemoteChecks = remote
421
+ .map((c, i) => ({ c, i }))
422
+ .filter(({ c, i }) => !matchedRemote.has(i) && (c.type || '').toUpperCase() === 'CHECK');
423
+ for (const { c: rc, i: ri } of unmatchedRemoteChecks) {
424
+ if (matchedRemote.has(ri))
425
+ continue;
426
+ if (!rc.name)
427
+ continue;
428
+ for (const { c: lc, i: li } of unmatchedLocalChecks) {
429
+ if (matchedLocal.has(li))
430
+ continue;
431
+ if (lc.name && (lc.name === rc.name ||
432
+ (lc.name.endsWith('_check') && rc.name.endsWith('_check') &&
433
+ lc.name.split('_').slice(-2).join('_') === rc.name.split('_').slice(-2).join('_')))) {
434
+ matchedLocal.add(li);
435
+ matchedRemote.add(ri);
436
+ break;
437
+ }
438
+ }
439
+ }
440
+ for (let ri = 0; ri < remote.length; ri++) {
441
+ if (!matchedRemote.has(ri)) {
442
+ const c = remote[ri];
356
443
  diffs.push({ name: c.name || c.definition || 'unnamed', type: 'added', after: toConstraintInfo(c) });
357
444
  }
358
445
  }
359
- for (const [s, c] of localSigs) {
360
- if (!remoteSigs.has(s)) {
446
+ for (let li = 0; li < local.length; li++) {
447
+ if (!matchedLocal.has(li)) {
448
+ const c = local[li];
361
449
  diffs.push({ name: c.name || c.definition || 'unnamed', type: 'removed', before: toConstraintInfo(c) });
362
450
  }
363
451
  }
@@ -531,32 +619,43 @@ export function formatCategorizedSummary(diff) {
531
619
  idxRemoved++;
532
620
  }
533
621
  for (const con of table.constraints || []) {
534
- const name = (con.name || '').toLowerCase();
535
- const isFk = name.includes('fkey') || name.includes('foreign');
536
- const isUq = name.includes('unique') || name.includes('_key');
537
- const isPk = name.includes('pkey') || name.includes('primary');
538
622
  const before = con.before;
539
623
  const after = con.after;
540
624
  const conType = (before?.type || after?.type || '').toUpperCase();
541
- if (conType === 'FOREIGN KEY' || isFk) {
625
+ let category = 'OTHER';
626
+ if (conType) {
627
+ category = conType;
628
+ }
629
+ else {
630
+ const name = (con.name || '').toLowerCase();
631
+ if (name.includes('pkey') || name.includes('primary'))
632
+ category = 'PRIMARY KEY';
633
+ else if (name.includes('fkey') || name.includes('foreign'))
634
+ category = 'FOREIGN KEY';
635
+ else if (name.includes('_check'))
636
+ category = 'CHECK';
637
+ else if (name.includes('_key') || name.includes('unique'))
638
+ category = 'UNIQUE';
639
+ }
640
+ if (category === 'FOREIGN KEY') {
542
641
  if (con.type === 'added')
543
642
  fkAdded++;
544
643
  else if (con.type === 'removed')
545
644
  fkRemoved++;
546
645
  }
547
- else if (conType === 'UNIQUE' || isUq) {
646
+ else if (category === 'UNIQUE') {
548
647
  if (con.type === 'added')
549
648
  uniqueAdded++;
550
649
  else if (con.type === 'removed')
551
650
  uniqueRemoved++;
552
651
  }
553
- else if (conType === 'PRIMARY KEY' || isPk) {
652
+ else if (category === 'PRIMARY KEY') {
554
653
  if (con.type === 'added')
555
654
  pkAdded++;
556
655
  else if (con.type === 'removed')
557
656
  pkRemoved++;
558
657
  }
559
- else if (conType === 'CHECK') {
658
+ else if (category === 'CHECK') {
560
659
  if (con.type === 'added')
561
660
  checkAdded++;
562
661
  else if (con.type === 'removed')
@@ -385,6 +385,18 @@ export function tableToAST(table) {
385
385
  col.isUnique = true;
386
386
  }
387
387
  }
388
+ const hasPKConstraint = constraints.some(c => c.type === 'PRIMARY KEY');
389
+ if (!hasPKConstraint) {
390
+ const pkCols = columns.filter(c => c.isPrimaryKey).map(c => c.name);
391
+ if (pkCols.length > 0) {
392
+ constraints.push({
393
+ name: `${table.$name}_pkey`,
394
+ type: 'PRIMARY KEY',
395
+ columns: pkCols,
396
+ trackingId: `${table.$name}_pkey`,
397
+ });
398
+ }
399
+ }
388
400
  return {
389
401
  name: table.$name,
390
402
  schema: table.$schema || 'public',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relq",
3
- "version": "1.0.91",
3
+ "version": "1.0.92",
4
4
  "description": "The Fully-Typed PostgreSQL ORM for TypeScript",
5
5
  "author": "Olajide Mathew O. <olajide.mathew@yuniq.solutions>",
6
6
  "license": "MIT",