mirror of
https://github.com/chartdb/chartdb.git
synced 2026-02-20 01:03:53 +08:00
1669 lines
53 KiB
TypeScript
1669 lines
53 KiB
TypeScript
import type { Diagram } from '@/lib/domain/diagram';
|
|
import type { DBField } from '@/lib/domain/db-field';
|
|
import type { DBIndex } from '@/lib/domain/db-index';
|
|
import type { DBTable } from '@/lib/domain/db-table';
|
|
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
|
import type { Area } from '@/lib/domain/area';
|
|
import type { Note } from '@/lib/domain/note';
|
|
import type { ChartDBDiff, DiffMap, DiffObject } from '@/lib/domain/diff/diff';
|
|
import type {
|
|
FieldDiff,
|
|
FieldDiffAttribute,
|
|
} from '@/lib/domain/diff/field-diff';
|
|
import type { TableDiff, TableDiffAttribute } from '../table-diff';
|
|
import type { AreaDiff, AreaDiffAttribute } from '../area-diff';
|
|
import type { NoteDiff, NoteDiffAttribute } from '../note-diff';
|
|
import type { IndexDiff, IndexDiffAttribute } from '../index-diff';
|
|
import type { RelationshipDiff } from '../relationship-diff';
|
|
import { areBooleansEqual } from '@/lib/utils';
|
|
|
|
export function getDiffMapKey({
|
|
diffObject,
|
|
objectId,
|
|
attribute,
|
|
}: {
|
|
diffObject: DiffObject;
|
|
objectId: string;
|
|
attribute?: string;
|
|
}): string {
|
|
return attribute
|
|
? `${diffObject}-${attribute}-${objectId}`
|
|
: `${diffObject}-${objectId}`;
|
|
}
|
|
|
|
const isOneOfDefined = (
|
|
...values: (string | number | boolean | undefined | null)[]
|
|
): boolean => {
|
|
return values.some((value) => value !== undefined && value !== null);
|
|
};
|
|
|
|
const normalizeBoolean = (value: boolean | undefined | null): boolean => {
|
|
return value === true;
|
|
};
|
|
|
|
/**
|
|
* Normalizes a comment/content string for comparison purposes.
|
|
* This handles cases where the same content differs only in whitespace formatting,
|
|
* such as newlines vs spaces, multiple spaces, or different line break styles.
|
|
*
|
|
* Examples that will be considered equal:
|
|
* - "| A | B" vs "| A\n| B"
|
|
* - "hello world" vs "hello world"
|
|
* - "line1\r\nline2" vs "line1\nline2"
|
|
*/
|
|
const normalizeComment = (
|
|
value: string | undefined | null
|
|
): string | undefined => {
|
|
if (value === undefined || value === null) {
|
|
return undefined;
|
|
}
|
|
// Replace all whitespace sequences (newlines, tabs, multiple spaces) with a single space
|
|
// Then trim leading/trailing whitespace
|
|
return value.replace(/\s+/g, ' ').trim();
|
|
};
|
|
|
|
/**
|
|
* Compares two comment/content strings in a whitespace-insensitive manner.
|
|
* Returns true if the comments are semantically different (i.e., a real change).
|
|
*/
|
|
const areCommentsDifferent = (
|
|
oldComment: string | undefined | null,
|
|
newComment: string | undefined | null
|
|
): boolean => {
|
|
const normalizedOld = normalizeComment(oldComment);
|
|
const normalizedNew = normalizeComment(newComment);
|
|
|
|
// Both undefined/empty means equal
|
|
if (!normalizedOld && !normalizedNew) {
|
|
return false;
|
|
}
|
|
|
|
// One defined, one not means different
|
|
if (!normalizedOld || !normalizedNew) {
|
|
return true;
|
|
}
|
|
|
|
// Compare normalized versions
|
|
return normalizedOld !== normalizedNew;
|
|
};
|
|
|
|
// Helper to determine if an attribute change should add to the changed map
|
|
// - undefined: always add (current behavior)
|
|
// - empty array: never add
|
|
// - array with values: only add if attribute is in the array
|
|
const shouldAddToChangedMap = <T>(
|
|
attribute: T,
|
|
changedAttributes?: T[]
|
|
): boolean => {
|
|
if (changedAttributes === undefined) {
|
|
return true;
|
|
}
|
|
if (changedAttributes.length === 0) {
|
|
return false;
|
|
}
|
|
return changedAttributes.includes(attribute);
|
|
};
|
|
|
|
export interface GenerateDiffOptions {
|
|
includeTables?: boolean;
|
|
includeFields?: boolean;
|
|
includeIndexes?: boolean;
|
|
includeRelationships?: boolean;
|
|
includeAreas?: boolean;
|
|
includeNotes?: boolean;
|
|
attributes?: {
|
|
tables?: TableDiffAttribute[];
|
|
fields?: FieldDiffAttribute[];
|
|
indexes?: IndexDiffAttribute[];
|
|
areas?: AreaDiffAttribute[];
|
|
notes?: NoteDiffAttribute[];
|
|
};
|
|
changedMaps?: {
|
|
changedTablesAttributes?: TableDiffAttribute[];
|
|
changedFieldsAttributes?: FieldDiffAttribute[];
|
|
changedIndexesAttributes?: IndexDiffAttribute[];
|
|
changedAreasAttributes?: AreaDiffAttribute[];
|
|
changedNotesAttributes?: NoteDiffAttribute[];
|
|
};
|
|
changeTypes?: {
|
|
tables?: TableDiff['type'][];
|
|
fields?: FieldDiff['type'][];
|
|
indexes?: IndexDiff['type'][];
|
|
relationships?: RelationshipDiff['type'][];
|
|
areas?: AreaDiff['type'][];
|
|
notes?: NoteDiff['type'][];
|
|
};
|
|
matchers?: {
|
|
table?: (table: DBTable, tables: DBTable[]) => DBTable | undefined;
|
|
field?: (field: DBField, fields: DBField[]) => DBField | undefined;
|
|
index?: (index: DBIndex, indexes: DBIndex[]) => DBIndex | undefined;
|
|
relationship?: (
|
|
relationship: DBRelationship,
|
|
relationships: DBRelationship[]
|
|
) => DBRelationship | undefined;
|
|
area?: (area: Area, areas: Area[]) => Area | undefined;
|
|
note?: (note: Note, notes: Note[]) => Note | undefined;
|
|
};
|
|
}
|
|
|
|
export function generateDiff({
|
|
diagram,
|
|
newDiagram,
|
|
options = {},
|
|
}: {
|
|
diagram: Diagram;
|
|
newDiagram: Diagram;
|
|
options?: GenerateDiffOptions;
|
|
}): {
|
|
diffMap: DiffMap;
|
|
changedTables: Map<string, boolean>;
|
|
changedFields: Map<string, boolean>;
|
|
changedIndexes: Map<string, boolean>;
|
|
changedAreas: Map<string, boolean>;
|
|
changedNotes: Map<string, boolean>;
|
|
} {
|
|
// Merge with default options
|
|
const mergedOptions: GenerateDiffOptions = {
|
|
includeTables: options.includeTables ?? true,
|
|
includeFields: options.includeFields ?? true,
|
|
includeIndexes: options.includeIndexes ?? true,
|
|
includeRelationships: options.includeRelationships ?? true,
|
|
includeAreas: options.includeAreas ?? false,
|
|
includeNotes: options.includeNotes ?? false,
|
|
attributes: options.attributes ?? {},
|
|
changedMaps: options.changedMaps,
|
|
changeTypes: options.changeTypes ?? {},
|
|
matchers: options.matchers ?? {},
|
|
};
|
|
|
|
const newDiffs = new Map<string, ChartDBDiff>();
|
|
const changedTables = new Map<string, boolean>();
|
|
const changedFields = new Map<string, boolean>();
|
|
const changedIndexes = new Map<string, boolean>();
|
|
const changedAreas = new Map<string, boolean>();
|
|
const changedNotes = new Map<string, boolean>();
|
|
|
|
// Use provided matchers or default ones
|
|
const tableMatcher = mergedOptions.matchers?.table ?? defaultTableMatcher;
|
|
const fieldMatcher = mergedOptions.matchers?.field ?? defaultFieldMatcher;
|
|
const indexMatcher = mergedOptions.matchers?.index ?? defaultIndexMatcher;
|
|
const relationshipMatcher =
|
|
mergedOptions.matchers?.relationship ?? defaultRelationshipMatcher;
|
|
const areaMatcher = mergedOptions.matchers?.area ?? defaultAreaMatcher;
|
|
const noteMatcher = mergedOptions.matchers?.note ?? defaultNoteMatcher;
|
|
|
|
// Compare tables
|
|
if (mergedOptions.includeTables) {
|
|
compareTables({
|
|
diagram,
|
|
newDiagram,
|
|
diffMap: newDiffs,
|
|
changedTables,
|
|
attributes: mergedOptions.attributes?.tables,
|
|
changedTablesAttributes:
|
|
mergedOptions.changedMaps?.changedTablesAttributes,
|
|
changeTypes: mergedOptions.changeTypes?.tables,
|
|
tableMatcher,
|
|
});
|
|
}
|
|
|
|
// Compare fields and indexes for matching tables
|
|
compareTableContents({
|
|
diagram,
|
|
newDiagram,
|
|
diffMap: newDiffs,
|
|
changedTables,
|
|
changedFields,
|
|
changedIndexes,
|
|
options: mergedOptions,
|
|
changedTablesAttributes:
|
|
mergedOptions.changedMaps?.changedTablesAttributes,
|
|
changedFieldsAttributes:
|
|
mergedOptions.changedMaps?.changedFieldsAttributes,
|
|
changedIndexesAttributes:
|
|
mergedOptions.changedMaps?.changedIndexesAttributes,
|
|
tableMatcher,
|
|
fieldMatcher,
|
|
indexMatcher,
|
|
});
|
|
|
|
// Compare relationships
|
|
if (mergedOptions.includeRelationships) {
|
|
compareRelationships({
|
|
diagram,
|
|
newDiagram,
|
|
diffMap: newDiffs,
|
|
changeTypes: mergedOptions.changeTypes?.relationships,
|
|
relationshipMatcher,
|
|
});
|
|
}
|
|
|
|
// Compare areas if enabled
|
|
if (mergedOptions.includeAreas) {
|
|
compareAreas({
|
|
diagram,
|
|
newDiagram,
|
|
diffMap: newDiffs,
|
|
changedAreas,
|
|
attributes: mergedOptions.attributes?.areas,
|
|
changedAreasAttributes:
|
|
mergedOptions.changedMaps?.changedAreasAttributes,
|
|
changeTypes: mergedOptions.changeTypes?.areas,
|
|
areaMatcher,
|
|
});
|
|
}
|
|
|
|
// Compare notes if enabled
|
|
if (mergedOptions.includeNotes) {
|
|
compareNotes({
|
|
diagram,
|
|
newDiagram,
|
|
diffMap: newDiffs,
|
|
changedNotes,
|
|
attributes: mergedOptions.attributes?.notes,
|
|
changedNotesAttributes:
|
|
mergedOptions.changedMaps?.changedNotesAttributes,
|
|
changeTypes: mergedOptions.changeTypes?.notes,
|
|
noteMatcher,
|
|
});
|
|
}
|
|
|
|
return {
|
|
diffMap: newDiffs,
|
|
changedTables,
|
|
changedFields,
|
|
changedIndexes,
|
|
changedAreas,
|
|
changedNotes,
|
|
};
|
|
}
|
|
|
|
// Compare tables between diagrams
|
|
function compareTables({
|
|
diagram,
|
|
newDiagram,
|
|
diffMap,
|
|
changedTables,
|
|
attributes,
|
|
changedTablesAttributes,
|
|
changeTypes,
|
|
tableMatcher,
|
|
}: {
|
|
diagram: Diagram;
|
|
newDiagram: Diagram;
|
|
diffMap: DiffMap;
|
|
changedTables: Map<string, boolean>;
|
|
attributes?: TableDiffAttribute[];
|
|
changedTablesAttributes?: TableDiffAttribute[];
|
|
changeTypes?: TableDiff['type'][];
|
|
tableMatcher: (table: DBTable, tables: DBTable[]) => DBTable | undefined;
|
|
}) {
|
|
const oldTables = diagram.tables || [];
|
|
const newTables = newDiagram.tables || [];
|
|
|
|
// If changeTypes is empty array, don't check any changes
|
|
if (changeTypes && changeTypes.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// If changeTypes is undefined, check all types
|
|
const typesToCheck = changeTypes ?? ['added', 'removed', 'changed'];
|
|
|
|
// Check for added tables
|
|
if (typesToCheck.includes('added')) {
|
|
for (const newTable of newTables) {
|
|
if (!tableMatcher(newTable, oldTables)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'table',
|
|
objectId: newTable.id,
|
|
}),
|
|
{
|
|
object: 'table',
|
|
type: 'added',
|
|
tableAdded: newTable,
|
|
}
|
|
);
|
|
changedTables.set(newTable.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for removed tables
|
|
if (typesToCheck.includes('removed')) {
|
|
for (const oldTable of oldTables) {
|
|
if (!tableMatcher(oldTable, newTables)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'table',
|
|
objectId: oldTable.id,
|
|
}),
|
|
{
|
|
object: 'table',
|
|
type: 'removed',
|
|
tableId: oldTable.id,
|
|
}
|
|
);
|
|
changedTables.set(oldTable.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for table name, comments and color changes
|
|
if (typesToCheck.includes('changed')) {
|
|
for (const oldTable of oldTables) {
|
|
const newTable = tableMatcher(oldTable, newTables);
|
|
|
|
if (!newTable) continue;
|
|
|
|
// If attributes are specified, only check those attributes
|
|
const attributesToCheck: TableDiffAttribute[] = attributes ?? [
|
|
'name',
|
|
'comments',
|
|
'color',
|
|
];
|
|
|
|
if (
|
|
attributesToCheck.includes('name') &&
|
|
oldTable.name !== newTable.name
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'table',
|
|
objectId: oldTable.id,
|
|
attribute: 'name',
|
|
}),
|
|
{
|
|
object: 'table',
|
|
type: 'changed',
|
|
tableId: oldTable.id,
|
|
newTableId: newTable.id,
|
|
attribute: 'name',
|
|
newValue: newTable.name,
|
|
oldValue: oldTable.name,
|
|
}
|
|
);
|
|
|
|
if (shouldAddToChangedMap('name', changedTablesAttributes)) {
|
|
changedTables.set(oldTable.id, true);
|
|
}
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('comments') &&
|
|
areCommentsDifferent(oldTable.comments, newTable.comments)
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'table',
|
|
objectId: oldTable.id,
|
|
attribute: 'comments',
|
|
}),
|
|
{
|
|
object: 'table',
|
|
type: 'changed',
|
|
tableId: oldTable.id,
|
|
newTableId: newTable.id,
|
|
attribute: 'comments',
|
|
newValue: newTable.comments,
|
|
oldValue: oldTable.comments,
|
|
}
|
|
);
|
|
|
|
if (
|
|
shouldAddToChangedMap('comments', changedTablesAttributes)
|
|
) {
|
|
changedTables.set(oldTable.id, true);
|
|
}
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('color') &&
|
|
oldTable.color !== newTable.color
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'table',
|
|
objectId: oldTable.id,
|
|
attribute: 'color',
|
|
}),
|
|
{
|
|
object: 'table',
|
|
type: 'changed',
|
|
tableId: oldTable.id,
|
|
newTableId: newTable.id,
|
|
attribute: 'color',
|
|
newValue: newTable.color,
|
|
oldValue: oldTable.color,
|
|
}
|
|
);
|
|
|
|
if (shouldAddToChangedMap('color', changedTablesAttributes)) {
|
|
changedTables.set(oldTable.id, true);
|
|
}
|
|
}
|
|
|
|
if (attributesToCheck.includes('x') && oldTable.x !== newTable.x) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'table',
|
|
objectId: oldTable.id,
|
|
attribute: 'x',
|
|
}),
|
|
{
|
|
object: 'table',
|
|
type: 'changed',
|
|
tableId: oldTable.id,
|
|
newTableId: newTable.id,
|
|
attribute: 'x',
|
|
newValue: newTable.x,
|
|
oldValue: oldTable.x,
|
|
}
|
|
);
|
|
|
|
if (shouldAddToChangedMap('x', changedTablesAttributes)) {
|
|
changedTables.set(oldTable.id, true);
|
|
}
|
|
}
|
|
|
|
if (attributesToCheck.includes('y') && oldTable.y !== newTable.y) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'table',
|
|
objectId: oldTable.id,
|
|
attribute: 'y',
|
|
}),
|
|
{
|
|
object: 'table',
|
|
type: 'changed',
|
|
tableId: oldTable.id,
|
|
newTableId: newTable.id,
|
|
attribute: 'y',
|
|
newValue: newTable.y,
|
|
oldValue: oldTable.y,
|
|
}
|
|
);
|
|
|
|
if (shouldAddToChangedMap('y', changedTablesAttributes)) {
|
|
changedTables.set(oldTable.id, true);
|
|
}
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('width') &&
|
|
oldTable.width !== newTable.width
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'table',
|
|
objectId: oldTable.id,
|
|
attribute: 'width',
|
|
}),
|
|
{
|
|
object: 'table',
|
|
type: 'changed',
|
|
tableId: oldTable.id,
|
|
newTableId: newTable.id,
|
|
attribute: 'width',
|
|
newValue: newTable.width,
|
|
oldValue: oldTable.width,
|
|
}
|
|
);
|
|
|
|
if (shouldAddToChangedMap('width', changedTablesAttributes)) {
|
|
changedTables.set(oldTable.id, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compare fields and indexes for matching tables
|
|
function compareTableContents({
|
|
diagram,
|
|
newDiagram,
|
|
diffMap,
|
|
changedTables,
|
|
changedFields,
|
|
changedIndexes,
|
|
options,
|
|
changedTablesAttributes,
|
|
changedFieldsAttributes,
|
|
changedIndexesAttributes,
|
|
tableMatcher,
|
|
fieldMatcher,
|
|
indexMatcher,
|
|
}: {
|
|
diagram: Diagram;
|
|
newDiagram: Diagram;
|
|
diffMap: DiffMap;
|
|
changedTables: Map<string, boolean>;
|
|
changedFields: Map<string, boolean>;
|
|
changedIndexes: Map<string, boolean>;
|
|
options?: GenerateDiffOptions;
|
|
changedTablesAttributes?: TableDiffAttribute[];
|
|
changedFieldsAttributes?: FieldDiffAttribute[];
|
|
changedIndexesAttributes?: IndexDiffAttribute[];
|
|
tableMatcher: (table: DBTable, tables: DBTable[]) => DBTable | undefined;
|
|
fieldMatcher: (field: DBField, fields: DBField[]) => DBField | undefined;
|
|
indexMatcher: (index: DBIndex, indexes: DBIndex[]) => DBIndex | undefined;
|
|
}) {
|
|
const oldTables = diagram.tables || [];
|
|
const newTables = newDiagram.tables || [];
|
|
|
|
// For each table that exists in both diagrams
|
|
for (const oldTable of oldTables) {
|
|
const newTable = tableMatcher(oldTable, newTables);
|
|
if (!newTable) continue;
|
|
|
|
// Compare fields
|
|
if (options?.includeFields) {
|
|
compareFields({
|
|
tableId: oldTable.id,
|
|
oldFields: oldTable.fields,
|
|
newFields: newTable.fields,
|
|
diffMap,
|
|
changedTables,
|
|
changedFields,
|
|
attributes: options?.attributes?.fields,
|
|
changedTablesAttributes,
|
|
changedFieldsAttributes,
|
|
changeTypes: options?.changeTypes?.fields,
|
|
fieldMatcher,
|
|
});
|
|
}
|
|
|
|
// Compare indexes
|
|
if (options?.includeIndexes) {
|
|
compareIndexes({
|
|
tableId: oldTable.id,
|
|
oldIndexes: oldTable.indexes,
|
|
newIndexes: newTable.indexes,
|
|
diffMap,
|
|
changedTables,
|
|
changedIndexes,
|
|
attributes: options?.attributes?.indexes,
|
|
changedTablesAttributes,
|
|
changedIndexesAttributes,
|
|
changeTypes: options?.changeTypes?.indexes,
|
|
indexMatcher,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compare fields between tables
|
|
function compareFields({
|
|
tableId,
|
|
oldFields,
|
|
newFields,
|
|
diffMap,
|
|
changedTables,
|
|
changedFields,
|
|
attributes,
|
|
changedTablesAttributes,
|
|
changedFieldsAttributes,
|
|
changeTypes,
|
|
fieldMatcher,
|
|
}: {
|
|
tableId: string;
|
|
oldFields: DBField[];
|
|
newFields: DBField[];
|
|
diffMap: DiffMap;
|
|
changedTables: Map<string, boolean>;
|
|
changedFields: Map<string, boolean>;
|
|
attributes?: FieldDiffAttribute[];
|
|
changedTablesAttributes?: TableDiffAttribute[];
|
|
changedFieldsAttributes?: FieldDiffAttribute[];
|
|
changeTypes?: FieldDiff['type'][];
|
|
fieldMatcher: (field: DBField, fields: DBField[]) => DBField | undefined;
|
|
}) {
|
|
// If changeTypes is empty array, don't check any changes
|
|
if (changeTypes && changeTypes.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// If changeTypes is undefined, check all types
|
|
const typesToCheck = changeTypes ?? ['added', 'removed', 'changed'];
|
|
// Check for added fields
|
|
if (typesToCheck.includes('added')) {
|
|
for (const newField of newFields) {
|
|
if (!fieldMatcher(newField, oldFields)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'field',
|
|
objectId: newField.id,
|
|
}),
|
|
{
|
|
object: 'field',
|
|
type: 'added',
|
|
newField,
|
|
tableId,
|
|
}
|
|
);
|
|
changedTables.set(tableId, true);
|
|
changedFields.set(newField.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for removed fields
|
|
if (typesToCheck.includes('removed')) {
|
|
for (const oldField of oldFields) {
|
|
if (!fieldMatcher(oldField, newFields)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'field',
|
|
objectId: oldField.id,
|
|
}),
|
|
{
|
|
object: 'field',
|
|
type: 'removed',
|
|
fieldId: oldField.id,
|
|
tableId,
|
|
}
|
|
);
|
|
|
|
changedTables.set(tableId, true);
|
|
changedFields.set(oldField.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for field changes
|
|
if (typesToCheck.includes('changed')) {
|
|
for (const oldField of oldFields) {
|
|
const newField = fieldMatcher(oldField, newFields);
|
|
if (!newField) continue;
|
|
|
|
// Compare basic field properties
|
|
compareFieldProperties({
|
|
tableId,
|
|
oldField,
|
|
newField,
|
|
diffMap,
|
|
changedTables,
|
|
changedFields,
|
|
attributes,
|
|
changedTablesAttributes,
|
|
changedFieldsAttributes,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compare field properties
|
|
function compareFieldProperties({
|
|
tableId,
|
|
oldField,
|
|
newField,
|
|
diffMap,
|
|
changedTables,
|
|
changedFields,
|
|
attributes,
|
|
changedTablesAttributes,
|
|
changedFieldsAttributes,
|
|
}: {
|
|
tableId: string;
|
|
oldField: DBField;
|
|
newField: DBField;
|
|
diffMap: DiffMap;
|
|
changedTables: Map<string, boolean>;
|
|
changedFields: Map<string, boolean>;
|
|
attributes?: FieldDiffAttribute[];
|
|
changedTablesAttributes?: TableDiffAttribute[];
|
|
changedFieldsAttributes?: FieldDiffAttribute[];
|
|
}) {
|
|
// If attributes are specified, only check those attributes
|
|
const attributesToCheck: FieldDiffAttribute[] = attributes ?? [
|
|
'name',
|
|
'type',
|
|
'primaryKey',
|
|
'unique',
|
|
'nullable',
|
|
'comments',
|
|
'characterMaximumLength',
|
|
'scale',
|
|
'precision',
|
|
'increment',
|
|
'isArray',
|
|
];
|
|
|
|
const changedAttributes: FieldDiffAttribute[] = [];
|
|
|
|
if (attributesToCheck.includes('name') && oldField.name !== newField.name) {
|
|
changedAttributes.push('name');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('type') &&
|
|
oldField.type.id !== newField.type.id
|
|
) {
|
|
changedAttributes.push('type');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('primaryKey') &&
|
|
oldField.primaryKey !== newField.primaryKey
|
|
) {
|
|
changedAttributes.push('primaryKey');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('unique') &&
|
|
oldField.unique !== newField.unique
|
|
) {
|
|
changedAttributes.push('unique');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('nullable') &&
|
|
oldField.nullable !== newField.nullable
|
|
) {
|
|
changedAttributes.push('nullable');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('comments') &&
|
|
areCommentsDifferent(oldField.comments, newField.comments)
|
|
) {
|
|
changedAttributes.push('comments');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('characterMaximumLength') &&
|
|
(newField.characterMaximumLength || oldField.characterMaximumLength) &&
|
|
oldField.characterMaximumLength !== newField.characterMaximumLength
|
|
) {
|
|
changedAttributes.push('characterMaximumLength');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('scale') &&
|
|
(newField.scale || oldField.scale) &&
|
|
oldField.scale !== newField.scale
|
|
) {
|
|
changedAttributes.push('scale');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('precision') &&
|
|
(newField.precision || oldField.precision) &&
|
|
oldField.precision !== newField.precision
|
|
) {
|
|
changedAttributes.push('precision');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('increment') &&
|
|
isOneOfDefined(newField.increment, oldField.increment) &&
|
|
normalizeBoolean(oldField.increment) !==
|
|
normalizeBoolean(newField.increment)
|
|
) {
|
|
changedAttributes.push('increment');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('isArray') &&
|
|
isOneOfDefined(newField.isArray, oldField.isArray) &&
|
|
normalizeBoolean(oldField.isArray) !==
|
|
normalizeBoolean(newField.isArray)
|
|
) {
|
|
changedAttributes.push('isArray');
|
|
}
|
|
|
|
if (changedAttributes.length > 0) {
|
|
// Track which attributes should trigger adding to changed maps
|
|
const attributesThatTriggerChange = changedAttributes.filter((attr) =>
|
|
shouldAddToChangedMap(attr, changedFieldsAttributes)
|
|
);
|
|
|
|
for (const attribute of changedAttributes) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'field',
|
|
objectId: oldField.id,
|
|
attribute,
|
|
}),
|
|
{
|
|
object: 'field',
|
|
type: 'changed',
|
|
fieldId: oldField.id,
|
|
newFieldId: newField.id,
|
|
tableId,
|
|
attribute,
|
|
oldValue: oldField[attribute] ?? '',
|
|
newValue: newField[attribute] ?? '',
|
|
}
|
|
);
|
|
}
|
|
|
|
// Only add to changed maps if at least one attribute should trigger a change
|
|
if (attributesThatTriggerChange.length > 0) {
|
|
// For changedTables, we need to check changedTablesAttributes
|
|
// undefined = always add, empty = never add, array = check if any field attribute qualifies
|
|
if (changedTablesAttributes === undefined) {
|
|
changedTables.set(tableId, true);
|
|
} else if (changedTablesAttributes.length > 0) {
|
|
// If changedTablesAttributes has values, we only add if explicitly configured
|
|
// Since these are field changes, we keep current behavior of adding to changedTables
|
|
changedTables.set(tableId, true);
|
|
}
|
|
// If changedTablesAttributes is empty array, don't add to changedTables
|
|
|
|
changedFields.set(oldField.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compare indexes between tables
|
|
function compareIndexes({
|
|
tableId,
|
|
oldIndexes,
|
|
newIndexes,
|
|
diffMap,
|
|
changedTables,
|
|
changedIndexes,
|
|
attributes,
|
|
changedTablesAttributes,
|
|
changedIndexesAttributes,
|
|
changeTypes,
|
|
indexMatcher,
|
|
}: {
|
|
tableId: string;
|
|
oldIndexes: DBIndex[];
|
|
newIndexes: DBIndex[];
|
|
diffMap: DiffMap;
|
|
changedTables: Map<string, boolean>;
|
|
changedIndexes: Map<string, boolean>;
|
|
attributes?: IndexDiffAttribute[];
|
|
changedTablesAttributes?: TableDiffAttribute[];
|
|
changedIndexesAttributes?: IndexDiffAttribute[];
|
|
changeTypes?: IndexDiff['type'][];
|
|
indexMatcher: (index: DBIndex, indexes: DBIndex[]) => DBIndex | undefined;
|
|
}) {
|
|
// If changeTypes is empty array, don't check any changes
|
|
if (changeTypes && changeTypes.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// If changeTypes is undefined, check all types
|
|
const typesToCheck = changeTypes ?? ['added', 'removed', 'changed'];
|
|
|
|
// For structural changes (added/removed indexes), add to changedTables unless
|
|
// changedTablesAttributes is explicitly set to empty array
|
|
const shouldAddToChangedTables =
|
|
changedTablesAttributes === undefined ||
|
|
changedTablesAttributes.length > 0;
|
|
|
|
// Check for added indexes
|
|
if (typesToCheck.includes('added')) {
|
|
for (const newIndex of newIndexes) {
|
|
if (!indexMatcher(newIndex, oldIndexes)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'index',
|
|
objectId: newIndex.id,
|
|
}),
|
|
{
|
|
object: 'index',
|
|
type: 'added',
|
|
newIndex,
|
|
tableId,
|
|
}
|
|
);
|
|
if (shouldAddToChangedTables) {
|
|
changedTables.set(tableId, true);
|
|
}
|
|
changedIndexes.set(newIndex.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for removed indexes
|
|
if (typesToCheck.includes('removed')) {
|
|
for (const oldIndex of oldIndexes) {
|
|
if (!indexMatcher(oldIndex, newIndexes)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'index',
|
|
objectId: oldIndex.id,
|
|
}),
|
|
{
|
|
object: 'index',
|
|
type: 'removed',
|
|
indexId: oldIndex.id,
|
|
tableId,
|
|
}
|
|
);
|
|
if (shouldAddToChangedTables) {
|
|
changedTables.set(tableId, true);
|
|
}
|
|
changedIndexes.set(oldIndex.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for index changes
|
|
if (typesToCheck.includes('changed')) {
|
|
for (const oldIndex of oldIndexes) {
|
|
const newIndex = indexMatcher(oldIndex, newIndexes);
|
|
if (!newIndex) continue;
|
|
|
|
compareIndexProperties({
|
|
tableId,
|
|
oldIndex,
|
|
newIndex,
|
|
diffMap,
|
|
changedTables,
|
|
changedIndexes,
|
|
attributes,
|
|
changedTablesAttributes,
|
|
changedIndexesAttributes,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper to compare fieldIds arrays
|
|
const areFieldIdsEqual = (
|
|
oldFieldIds: string[],
|
|
newFieldIds: string[]
|
|
): boolean => {
|
|
if (oldFieldIds.length !== newFieldIds.length) {
|
|
return false;
|
|
}
|
|
for (let i = 0; i < oldFieldIds.length; i++) {
|
|
if (oldFieldIds[i] !== newFieldIds[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
// Compare index properties
|
|
function compareIndexProperties({
|
|
tableId,
|
|
oldIndex,
|
|
newIndex,
|
|
diffMap,
|
|
changedTables,
|
|
changedIndexes,
|
|
attributes,
|
|
changedTablesAttributes,
|
|
changedIndexesAttributes,
|
|
}: {
|
|
tableId: string;
|
|
oldIndex: DBIndex;
|
|
newIndex: DBIndex;
|
|
diffMap: DiffMap;
|
|
changedTables: Map<string, boolean>;
|
|
changedIndexes: Map<string, boolean>;
|
|
attributes?: IndexDiffAttribute[];
|
|
changedTablesAttributes?: TableDiffAttribute[];
|
|
changedIndexesAttributes?: IndexDiffAttribute[];
|
|
}) {
|
|
// If attributes are specified, only check those attributes
|
|
const attributesToCheck: IndexDiffAttribute[] = attributes ?? [
|
|
'name',
|
|
'unique',
|
|
'fieldIds',
|
|
'type',
|
|
];
|
|
|
|
const changedAttributes: IndexDiffAttribute[] = [];
|
|
|
|
if (attributesToCheck.includes('name') && oldIndex.name !== newIndex.name) {
|
|
changedAttributes.push('name');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('unique') &&
|
|
oldIndex.unique !== newIndex.unique
|
|
) {
|
|
changedAttributes.push('unique');
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('fieldIds') &&
|
|
!areFieldIdsEqual(oldIndex.fieldIds, newIndex.fieldIds)
|
|
) {
|
|
changedAttributes.push('fieldIds');
|
|
}
|
|
|
|
if (attributesToCheck.includes('type') && oldIndex.type !== newIndex.type) {
|
|
changedAttributes.push('type');
|
|
}
|
|
|
|
if (changedAttributes.length > 0) {
|
|
// Track which attributes should trigger adding to changed maps
|
|
const attributesThatTriggerChange = changedAttributes.filter((attr) =>
|
|
shouldAddToChangedMap(attr, changedIndexesAttributes)
|
|
);
|
|
|
|
for (const attribute of changedAttributes) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'index',
|
|
objectId: oldIndex.id,
|
|
attribute,
|
|
}),
|
|
{
|
|
object: 'index',
|
|
type: 'changed',
|
|
indexId: oldIndex.id,
|
|
newIndexId: newIndex.id,
|
|
tableId,
|
|
attribute,
|
|
oldValue: oldIndex[attribute],
|
|
newValue: newIndex[attribute],
|
|
}
|
|
);
|
|
}
|
|
|
|
// Only add to changed maps if at least one attribute should trigger a change
|
|
if (attributesThatTriggerChange.length > 0) {
|
|
// For changedTables, we need to check changedTablesAttributes
|
|
if (changedTablesAttributes === undefined) {
|
|
changedTables.set(tableId, true);
|
|
} else if (changedTablesAttributes.length > 0) {
|
|
changedTables.set(tableId, true);
|
|
}
|
|
|
|
changedIndexes.set(oldIndex.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compare relationships between diagrams
|
|
function compareRelationships({
|
|
diagram,
|
|
newDiagram,
|
|
diffMap,
|
|
changeTypes,
|
|
relationshipMatcher,
|
|
}: {
|
|
diagram: Diagram;
|
|
newDiagram: Diagram;
|
|
diffMap: DiffMap;
|
|
changeTypes?: RelationshipDiff['type'][];
|
|
relationshipMatcher: (
|
|
relationship: DBRelationship,
|
|
relationships: DBRelationship[]
|
|
) => DBRelationship | undefined;
|
|
}) {
|
|
// If changeTypes is empty array, don't check any changes
|
|
if (changeTypes && changeTypes.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// If changeTypes is undefined, check all types
|
|
const typesToCheck = changeTypes ?? ['added', 'removed'];
|
|
const oldRelationships = diagram.relationships || [];
|
|
const newRelationships = newDiagram.relationships || [];
|
|
|
|
// Check for added relationships
|
|
if (typesToCheck.includes('added')) {
|
|
for (const newRelationship of newRelationships) {
|
|
if (!relationshipMatcher(newRelationship, oldRelationships)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'relationship',
|
|
objectId: newRelationship.id,
|
|
}),
|
|
{
|
|
object: 'relationship',
|
|
type: 'added',
|
|
newRelationship,
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for removed relationships
|
|
if (typesToCheck.includes('removed')) {
|
|
for (const oldRelationship of oldRelationships) {
|
|
if (!relationshipMatcher(oldRelationship, newRelationships)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'relationship',
|
|
objectId: oldRelationship.id,
|
|
}),
|
|
{
|
|
object: 'relationship',
|
|
type: 'removed',
|
|
relationshipId: oldRelationship.id,
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compare areas between diagrams
|
|
function compareAreas({
|
|
diagram,
|
|
newDiagram,
|
|
diffMap,
|
|
changedAreas,
|
|
attributes,
|
|
changedAreasAttributes,
|
|
changeTypes,
|
|
areaMatcher,
|
|
}: {
|
|
diagram: Diagram;
|
|
newDiagram: Diagram;
|
|
diffMap: DiffMap;
|
|
changedAreas: Map<string, boolean>;
|
|
attributes?: AreaDiffAttribute[];
|
|
changedAreasAttributes?: AreaDiffAttribute[];
|
|
changeTypes?: AreaDiff['type'][];
|
|
areaMatcher: (area: Area, areas: Area[]) => Area | undefined;
|
|
}) {
|
|
const oldAreas = diagram.areas || [];
|
|
const newAreas = newDiagram.areas || [];
|
|
|
|
// If changeTypes is empty array, don't check any changes
|
|
if (changeTypes && changeTypes.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// If changeTypes is undefined, check all types
|
|
const typesToCheck = changeTypes ?? ['added', 'removed', 'changed'];
|
|
|
|
// Check for added areas
|
|
if (typesToCheck.includes('added')) {
|
|
for (const newArea of newAreas) {
|
|
if (!areaMatcher(newArea, oldAreas)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'area',
|
|
objectId: newArea.id,
|
|
}),
|
|
{
|
|
object: 'area',
|
|
type: 'added',
|
|
areaAdded: newArea,
|
|
}
|
|
);
|
|
changedAreas.set(newArea.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for removed areas
|
|
if (typesToCheck.includes('removed')) {
|
|
for (const oldArea of oldAreas) {
|
|
if (!areaMatcher(oldArea, newAreas)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'area',
|
|
objectId: oldArea.id,
|
|
}),
|
|
{
|
|
object: 'area',
|
|
type: 'removed',
|
|
areaId: oldArea.id,
|
|
}
|
|
);
|
|
changedAreas.set(oldArea.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for area name and color changes
|
|
if (typesToCheck.includes('changed')) {
|
|
for (const oldArea of oldAreas) {
|
|
const newArea = areaMatcher(oldArea, newAreas);
|
|
|
|
if (!newArea) continue;
|
|
|
|
// If attributes are specified, only check those attributes
|
|
const attributesToCheck: AreaDiffAttribute[] = attributes ?? [
|
|
'name',
|
|
'color',
|
|
];
|
|
|
|
if (
|
|
attributesToCheck.includes('name') &&
|
|
oldArea.name !== newArea.name
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'area',
|
|
objectId: oldArea.id,
|
|
attribute: 'name',
|
|
}),
|
|
{
|
|
object: 'area',
|
|
type: 'changed',
|
|
areaId: oldArea.id,
|
|
newAreaId: newArea.id,
|
|
attribute: 'name',
|
|
newValue: newArea.name,
|
|
oldValue: oldArea.name,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('name', changedAreasAttributes)) {
|
|
changedAreas.set(oldArea.id, true);
|
|
}
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('color') &&
|
|
oldArea.color !== newArea.color
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'area',
|
|
objectId: oldArea.id,
|
|
attribute: 'color',
|
|
}),
|
|
{
|
|
object: 'area',
|
|
type: 'changed',
|
|
areaId: oldArea.id,
|
|
newAreaId: newArea.id,
|
|
attribute: 'color',
|
|
newValue: newArea.color,
|
|
oldValue: oldArea.color,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('color', changedAreasAttributes)) {
|
|
changedAreas.set(oldArea.id, true);
|
|
}
|
|
}
|
|
|
|
if (attributesToCheck.includes('x') && oldArea.x !== newArea.x) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'area',
|
|
objectId: oldArea.id,
|
|
attribute: 'x',
|
|
}),
|
|
{
|
|
object: 'area',
|
|
type: 'changed',
|
|
areaId: oldArea.id,
|
|
newAreaId: newArea.id,
|
|
attribute: 'x',
|
|
newValue: newArea.x,
|
|
oldValue: oldArea.x,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('x', changedAreasAttributes)) {
|
|
changedAreas.set(oldArea.id, true);
|
|
}
|
|
}
|
|
|
|
if (attributesToCheck.includes('y') && oldArea.y !== newArea.y) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'area',
|
|
objectId: oldArea.id,
|
|
attribute: 'y',
|
|
}),
|
|
{
|
|
object: 'area',
|
|
type: 'changed',
|
|
areaId: oldArea.id,
|
|
newAreaId: newArea.id,
|
|
attribute: 'y',
|
|
newValue: newArea.y,
|
|
oldValue: oldArea.y,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('y', changedAreasAttributes)) {
|
|
changedAreas.set(oldArea.id, true);
|
|
}
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('width') &&
|
|
oldArea.width !== newArea.width
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'area',
|
|
objectId: oldArea.id,
|
|
attribute: 'width',
|
|
}),
|
|
{
|
|
object: 'area',
|
|
type: 'changed',
|
|
areaId: oldArea.id,
|
|
newAreaId: newArea.id,
|
|
attribute: 'width',
|
|
newValue: newArea.width,
|
|
oldValue: oldArea.width,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('width', changedAreasAttributes)) {
|
|
changedAreas.set(oldArea.id, true);
|
|
}
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('height') &&
|
|
oldArea.height !== newArea.height
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'area',
|
|
objectId: oldArea.id,
|
|
attribute: 'height',
|
|
}),
|
|
{
|
|
object: 'area',
|
|
type: 'changed',
|
|
areaId: oldArea.id,
|
|
newAreaId: newArea.id,
|
|
attribute: 'height',
|
|
newValue: newArea.height,
|
|
oldValue: oldArea.height,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('height', changedAreasAttributes)) {
|
|
changedAreas.set(oldArea.id, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compare notes between diagrams
|
|
function compareNotes({
|
|
diagram,
|
|
newDiagram,
|
|
diffMap,
|
|
changedNotes,
|
|
attributes,
|
|
changedNotesAttributes,
|
|
changeTypes,
|
|
noteMatcher,
|
|
}: {
|
|
diagram: Diagram;
|
|
newDiagram: Diagram;
|
|
diffMap: DiffMap;
|
|
changedNotes: Map<string, boolean>;
|
|
attributes?: NoteDiffAttribute[];
|
|
changedNotesAttributes?: NoteDiffAttribute[];
|
|
changeTypes?: NoteDiff['type'][];
|
|
noteMatcher: (note: Note, notes: Note[]) => Note | undefined;
|
|
}) {
|
|
const oldNotes = diagram.notes || [];
|
|
const newNotes = newDiagram.notes || [];
|
|
|
|
// If changeTypes is empty array, don't check any changes
|
|
if (changeTypes && changeTypes.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// If changeTypes is undefined, check all types
|
|
const typesToCheck = changeTypes ?? ['added', 'removed', 'changed'];
|
|
|
|
// Check for added notes
|
|
if (typesToCheck.includes('added')) {
|
|
for (const newNote of newNotes) {
|
|
if (!noteMatcher(newNote, oldNotes)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'note',
|
|
objectId: newNote.id,
|
|
}),
|
|
{
|
|
object: 'note',
|
|
type: 'added',
|
|
noteAdded: newNote,
|
|
}
|
|
);
|
|
changedNotes.set(newNote.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for removed notes
|
|
if (typesToCheck.includes('removed')) {
|
|
for (const oldNote of oldNotes) {
|
|
if (!noteMatcher(oldNote, newNotes)) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'note',
|
|
objectId: oldNote.id,
|
|
}),
|
|
{
|
|
object: 'note',
|
|
type: 'removed',
|
|
noteId: oldNote.id,
|
|
}
|
|
);
|
|
changedNotes.set(oldNote.id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for note content and color changes
|
|
if (typesToCheck.includes('changed')) {
|
|
for (const oldNote of oldNotes) {
|
|
const newNote = noteMatcher(oldNote, newNotes);
|
|
|
|
if (!newNote) continue;
|
|
|
|
// If attributes are specified, only check those attributes
|
|
const attributesToCheck: NoteDiffAttribute[] = attributes ?? [
|
|
'content',
|
|
'color',
|
|
];
|
|
|
|
if (
|
|
attributesToCheck.includes('content') &&
|
|
areCommentsDifferent(oldNote.content, newNote.content)
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'note',
|
|
objectId: oldNote.id,
|
|
attribute: 'content',
|
|
}),
|
|
{
|
|
object: 'note',
|
|
type: 'changed',
|
|
noteId: oldNote.id,
|
|
newNoteId: newNote.id,
|
|
attribute: 'content',
|
|
newValue: newNote.content,
|
|
oldValue: oldNote.content,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('content', changedNotesAttributes)) {
|
|
changedNotes.set(oldNote.id, true);
|
|
}
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('color') &&
|
|
oldNote.color !== newNote.color
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'note',
|
|
objectId: oldNote.id,
|
|
attribute: 'color',
|
|
}),
|
|
{
|
|
object: 'note',
|
|
type: 'changed',
|
|
noteId: oldNote.id,
|
|
newNoteId: newNote.id,
|
|
attribute: 'color',
|
|
newValue: newNote.color,
|
|
oldValue: oldNote.color,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('color', changedNotesAttributes)) {
|
|
changedNotes.set(oldNote.id, true);
|
|
}
|
|
}
|
|
|
|
if (attributesToCheck.includes('x') && oldNote.x !== newNote.x) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'note',
|
|
objectId: oldNote.id,
|
|
attribute: 'x',
|
|
}),
|
|
{
|
|
object: 'note',
|
|
type: 'changed',
|
|
noteId: oldNote.id,
|
|
newNoteId: newNote.id,
|
|
attribute: 'x',
|
|
newValue: newNote.x,
|
|
oldValue: oldNote.x,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('x', changedNotesAttributes)) {
|
|
changedNotes.set(oldNote.id, true);
|
|
}
|
|
}
|
|
|
|
if (attributesToCheck.includes('y') && oldNote.y !== newNote.y) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'note',
|
|
objectId: oldNote.id,
|
|
attribute: 'y',
|
|
}),
|
|
{
|
|
object: 'note',
|
|
type: 'changed',
|
|
noteId: oldNote.id,
|
|
newNoteId: newNote.id,
|
|
attribute: 'y',
|
|
newValue: newNote.y,
|
|
oldValue: oldNote.y,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('y', changedNotesAttributes)) {
|
|
changedNotes.set(oldNote.id, true);
|
|
}
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('width') &&
|
|
oldNote.width !== newNote.width
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'note',
|
|
objectId: oldNote.id,
|
|
attribute: 'width',
|
|
}),
|
|
{
|
|
object: 'note',
|
|
type: 'changed',
|
|
noteId: oldNote.id,
|
|
newNoteId: newNote.id,
|
|
attribute: 'width',
|
|
newValue: newNote.width,
|
|
oldValue: oldNote.width,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('width', changedNotesAttributes)) {
|
|
changedNotes.set(oldNote.id, true);
|
|
}
|
|
}
|
|
|
|
if (
|
|
attributesToCheck.includes('height') &&
|
|
oldNote.height !== newNote.height
|
|
) {
|
|
diffMap.set(
|
|
getDiffMapKey({
|
|
diffObject: 'note',
|
|
objectId: oldNote.id,
|
|
attribute: 'height',
|
|
}),
|
|
{
|
|
object: 'note',
|
|
type: 'changed',
|
|
noteId: oldNote.id,
|
|
newNoteId: newNote.id,
|
|
attribute: 'height',
|
|
newValue: newNote.height,
|
|
oldValue: oldNote.height,
|
|
}
|
|
);
|
|
if (shouldAddToChangedMap('height', changedNotesAttributes)) {
|
|
changedNotes.set(oldNote.id, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const defaultTableMatcher = (
|
|
table: DBTable,
|
|
tables: DBTable[]
|
|
): DBTable | undefined => {
|
|
return tables.find((t) => t.id === table.id);
|
|
};
|
|
|
|
const defaultFieldMatcher = (
|
|
field: DBField,
|
|
fields: DBField[]
|
|
): DBField | undefined => {
|
|
return fields.find((f) => f.id === field.id);
|
|
};
|
|
|
|
const defaultIndexMatcher = (
|
|
index: DBIndex,
|
|
indexes: DBIndex[]
|
|
): DBIndex | undefined => {
|
|
// Priority 1: Match by ID
|
|
const byId = indexes.find((i) => i.id === index.id);
|
|
if (byId) {
|
|
return byId;
|
|
}
|
|
|
|
// Priority 2: Match by name (only if unique match)
|
|
if (index.name) {
|
|
const byName = indexes.filter(
|
|
(i) =>
|
|
i.name === index.name &&
|
|
areBooleansEqual(i.isPrimaryKey, index.isPrimaryKey)
|
|
);
|
|
if (byName.length === 1) {
|
|
return byName[0];
|
|
}
|
|
}
|
|
|
|
// Priority 3: Match by fieldIds (only if unique match)
|
|
const byFieldIds = indexes.filter(
|
|
(i) =>
|
|
areFieldIdsEqual(i.fieldIds, index.fieldIds) &&
|
|
areBooleansEqual(i.isPrimaryKey, index.isPrimaryKey)
|
|
);
|
|
if (byFieldIds.length === 1) {
|
|
return byFieldIds[0];
|
|
}
|
|
|
|
return undefined;
|
|
};
|
|
|
|
const defaultRelationshipMatcher = (
|
|
relationship: DBRelationship,
|
|
relationships: DBRelationship[]
|
|
): DBRelationship | undefined => {
|
|
return relationships.find((r) => r.id === relationship.id);
|
|
};
|
|
|
|
const defaultAreaMatcher = (area: Area, areas: Area[]): Area | undefined => {
|
|
return areas.find((a) => a.id === area.id);
|
|
};
|
|
|
|
const defaultNoteMatcher = (note: Note, notes: Note[]): Note | undefined => {
|
|
return notes.find((n) => n.id === note.id);
|
|
};
|