lib refactor (#744)

This commit is contained in:
Guy Ben-Aharon
2025-06-10 11:04:51 +03:00
committed by GitHub
parent 58acb65f12
commit 433c68a33d
20 changed files with 227 additions and 131 deletions

View File

@@ -6,7 +6,10 @@ import type {
} from './diff-context';
import { diffContext } from './diff-context';
import { generateDiff, getDiffMapKey } from './diff-check/diff-check';
import {
generateDiff,
getDiffMapKey,
} from '@/lib/domain/diff/diff-check/diff-check';
import type { Diagram } from '@/lib/domain/diagram';
import { useEventEmitter } from 'ahooks';
import type { DBField } from '@/lib/domain/db-field';

View File

@@ -36,6 +36,7 @@ import { useDebounce } from '@/hooks/use-debounce-v2';
import { InstructionsSection } from './instructions-section/instructions-section';
import { parseSQLError } from '@/lib/data/sql-import';
import type { editor } from 'monaco-editor';
import { waitFor } from '@/lib/utils';
const errorScriptOutputMessage =
'Invalid JSON. Please correct it or contact us at support@chartdb.io for help.';
@@ -211,7 +212,8 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
const handleCheckJson = useCallback(async () => {
setIsCheckingJson(true);
const fixedJson = await fixMetadataJson(scriptResult);
await waitFor(1000);
const fixedJson = fixMetadataJson(scriptResult);
if (isStringMetadataJson(fixedJson)) {
setScriptResult(fixedJson);

View File

@@ -124,7 +124,7 @@ export const cloneDiagram = (
} = {
generateId: defaultGenerateId,
}
): Diagram => {
): { diagram: Diagram; idsMap: Map<string, string> } => {
const { generateId } = options;
const diagramId = generateId();
@@ -214,13 +214,20 @@ export const cloneDiagram = (
.filter((area): area is Area => area !== null) ?? [];
return {
...diagram,
id: diagramId,
dependencies,
relationships,
tables,
areas,
createdAt: new Date(),
updatedAt: new Date(),
diagram: {
...diagram,
id: diagramId,
dependencies,
relationships,
tables,
areas,
createdAt: diagram.createdAt
? new Date(diagram.createdAt)
: new Date(),
updatedAt: diagram.updatedAt
? new Date(diagram.updatedAt)
: new Date(),
},
idsMap,
};
};

View File

@@ -1,11 +1,6 @@
import { waitFor } from '@/lib/utils';
import { isDatabaseMetadata } from './metadata-types/database-metadata';
export const fixMetadataJson = async (
metadataJson: string
): Promise<string> => {
await waitFor(1000);
export const fixMetadataJson = (metadataJson: string): string => {
// Replace problematic array default values with null
metadataJson = metadataJson.replace(
/"default": "?'?\[[^\]]*\]'?"?(\\")?(,|\})/gs,

View File

@@ -11,18 +11,18 @@ import type { AST } from 'node-sql-parser';
export interface DBDependency {
id: string;
schema?: string;
schema?: string | null;
tableId: string;
dependentSchema?: string;
dependentSchema?: string | null;
dependentTableId: string;
createdAt: number;
}
export const dbDependencySchema: z.ZodType<DBDependency> = z.object({
id: z.string(),
schema: z.string().optional(),
schema: z.string().or(z.null()).optional(),
tableId: z.string(),
dependentSchema: z.string().optional(),
dependentSchema: z.string().or(z.null()).optional(),
dependentTableId: z.string(),
createdAt: z.number(),
});

View File

@@ -14,14 +14,14 @@ export interface DBField {
primaryKey: boolean;
unique: boolean;
nullable: boolean;
increment?: boolean;
increment?: boolean | null;
createdAt: number;
characterMaximumLength?: string;
precision?: number;
scale?: number;
default?: string;
collation?: string;
comments?: string;
characterMaximumLength?: string | null;
precision?: number | null;
scale?: number | null;
default?: string | null;
collation?: string | null;
comments?: string | null;
}
export const dbFieldSchema: z.ZodType<DBField> = z.object({
@@ -31,14 +31,14 @@ export const dbFieldSchema: z.ZodType<DBField> = z.object({
primaryKey: z.boolean(),
unique: z.boolean(),
nullable: z.boolean(),
increment: z.boolean().optional(),
increment: z.boolean().or(z.null()).optional(),
createdAt: z.number(),
characterMaximumLength: z.string().optional(),
precision: z.number().optional(),
scale: z.number().optional(),
default: z.string().optional(),
collation: z.string().optional(),
comments: z.string().optional(),
characterMaximumLength: z.string().or(z.null()).optional(),
precision: z.number().or(z.null()).optional(),
scale: z.number().or(z.null()).optional(),
default: z.string().or(z.null()).optional(),
collation: z.string().or(z.null()).optional(),
comments: z.string().or(z.null()).optional(),
});
export const createFieldsFromMetadata = ({

View File

@@ -11,9 +11,9 @@ import { generateId } from '@/lib/utils';
export interface DBRelationship {
id: string;
name: string;
sourceSchema?: string;
sourceSchema?: string | null;
sourceTableId: string;
targetSchema?: string;
targetSchema?: string | null;
targetTableId: string;
sourceFieldId: string;
targetFieldId: string;
@@ -25,9 +25,9 @@ export interface DBRelationship {
export const dbRelationshipSchema: z.ZodType<DBRelationship> = z.object({
id: z.string(),
name: z.string(),
sourceSchema: z.string().optional(),
sourceSchema: z.string().or(z.null()).optional(),
sourceTableId: z.string(),
targetSchema: z.string().optional(),
targetSchema: z.string().or(z.null()).optional(),
targetTableId: z.string(),
sourceFieldId: z.string(),
targetFieldId: z.string(),

View File

@@ -34,37 +34,37 @@ export const TABLE_MINIMIZED_FIELDS = 10;
export interface DBTable {
id: string;
name: string;
schema?: string;
schema?: string | null;
x: number;
y: number;
fields: DBField[];
indexes: DBIndex[];
color: string;
isView: boolean;
isMaterializedView?: boolean;
isMaterializedView?: boolean | null;
createdAt: number;
width?: number;
comments?: string;
order?: number;
expanded?: boolean;
width?: number | null;
comments?: string | null;
order?: number | null;
expanded?: boolean | null;
}
export const dbTableSchema: z.ZodType<DBTable> = z.object({
id: z.string(),
name: z.string(),
schema: z.string().optional(),
schema: z.string().or(z.null()).optional(),
x: z.number(),
y: z.number(),
fields: z.array(dbFieldSchema),
indexes: z.array(dbIndexSchema),
color: z.string(),
isView: z.boolean(),
isMaterializedView: z.boolean().optional(),
isMaterializedView: z.boolean().or(z.null()).optional(),
createdAt: z.number(),
width: z.number().optional(),
comments: z.string().optional(),
order: z.number().optional(),
expanded: z.boolean().optional(),
width: z.number().or(z.null()).optional(),
comments: z.string().or(z.null()).optional(),
order: z.number().or(z.null()).optional(),
expanded: z.boolean().or(z.null()).optional(),
});
export const shouldShowTablesBySchemaFilter = (

View File

@@ -1,27 +1,59 @@
import { z } from 'zod';
import type { FieldDiff } from './field-diff';
import { fieldDiffSchema } from './field-diff';
import { createFieldDiffSchema } from './field-diff';
import type { IndexDiff } from './index-diff';
import { indexDiffSchema } from './index-diff';
import { createIndexDiffSchema } from './index-diff';
import type { RelationshipDiff } from './relationship-diff';
import { relationshipDiffSchema } from './relationship-diff';
import { createRelationshipDiffSchema } from './relationship-diff';
import type { TableDiff } from './table-diff';
import { tableDiffSchema } from './table-diff';
import { createTableDiffSchema } from './table-diff';
import type { DBField, DBIndex, DBRelationship, DBTable } from '..';
export type ChartDBDiff = TableDiff | FieldDiff | IndexDiff | RelationshipDiff;
export type ChartDBDiff<
TTable = DBTable,
TField = DBField,
TIndex = DBIndex,
TRelationship = DBRelationship,
> =
| TableDiff<TTable>
| FieldDiff<TField>
| IndexDiff<TIndex>
| RelationshipDiff<TRelationship>;
export const chartDBDiffSchema: z.ZodType<ChartDBDiff> = z.union([
tableDiffSchema,
fieldDiffSchema,
indexDiffSchema,
relationshipDiffSchema,
]);
export const createChartDBDiffSchema = <
TTable = DBTable,
TField = DBField,
TIndex = DBIndex,
TRelationship = DBRelationship,
>(
tableSchema: z.ZodType<TTable>,
fieldSchema: z.ZodType<TField>,
indexSchema: z.ZodType<TIndex>,
relationshipSchema: z.ZodType<TRelationship>
): z.ZodType<ChartDBDiff<TTable, TField, TIndex, TRelationship>> => {
return z.union([
createTableDiffSchema(tableSchema),
createFieldDiffSchema(fieldSchema),
createIndexDiffSchema(indexSchema),
createRelationshipDiffSchema(relationshipSchema),
]) as z.ZodType<ChartDBDiff<TTable, TField, TIndex, TRelationship>>;
};
export type DiffMap = Map<string, ChartDBDiff>;
export type DiffMap<
TTable = DBTable,
TField = DBField,
TIndex = DBIndex,
TRelationship = DBRelationship,
> = Map<string, ChartDBDiff<TTable, TField, TIndex, TRelationship>>;
export type DiffObject =
| TableDiff['object']
| FieldDiff['object']
| IndexDiff['object']
| RelationshipDiff['object'];
export type DiffObject<
TTable = DBTable,
TField = DBField,
TIndex = DBIndex,
TRelationship = DBRelationship,
> =
| TableDiff<TTable>['object']
| FieldDiff<TField>['object']
| IndexDiff<TIndex>['object']
| RelationshipDiff<TRelationship>['object'];

View File

@@ -5,7 +5,6 @@ import {
dataTypeSchema,
} from '@/lib/data/data-types/data-types';
import type { DBField } from '../db-field';
import { dbFieldSchema } from '../db-field';
export type FieldDiffAttribute =
| 'name'
@@ -24,19 +23,23 @@ export const fieldDiffAttributeSchema: z.ZodType<FieldDiffAttribute> = z.union([
z.literal('comments'),
]);
export interface FieldDiffAdded {
export interface FieldDiffAdded<T = DBField> {
object: 'field';
type: 'added';
tableId: string;
newField: DBField;
newField: T;
}
export const fieldDiffAddedSchema: z.ZodType<FieldDiffAdded> = z.object({
object: z.literal('field'),
type: z.literal('added'),
tableId: z.string(),
newField: dbFieldSchema,
});
export const createFieldDiffAddedSchema = <T = DBField>(
fieldSchema: z.ZodType<T>
): z.ZodType<FieldDiffAdded<T>> => {
return z.object({
object: z.literal('field'),
type: z.literal('added'),
tableId: z.string(),
newField: fieldSchema,
}) as z.ZodType<FieldDiffAdded<T>>;
};
export interface FieldDiffRemoved {
object: 'field';
@@ -72,10 +75,17 @@ export const fieldDiffChangedSchema: z.ZodType<FieldDiffChanged> = z.object({
newValue: z.union([z.string(), z.boolean(), dataTypeSchema]),
});
export type FieldDiff = FieldDiffAdded | FieldDiffRemoved | FieldDiffChanged;
export type FieldDiff<T = DBField> =
| FieldDiffAdded<T>
| FieldDiffRemoved
| FieldDiffChanged;
export const fieldDiffSchema: z.ZodType<FieldDiff> = z.union([
fieldDiffRemovedSchema,
fieldDiffChangedSchema,
fieldDiffAddedSchema,
]);
export const createFieldDiffSchema = <T = DBField>(
fieldSchema: z.ZodType<T>
): z.ZodType<FieldDiff<T>> => {
return z.union([
fieldDiffRemovedSchema,
fieldDiffChangedSchema,
createFieldDiffAddedSchema(fieldSchema),
]) as z.ZodType<FieldDiff<T>>;
};

View File

@@ -1,20 +1,23 @@
import { z } from 'zod';
import type { DBIndex } from '../db-index';
import { dbIndexSchema } from '../db-index';
export interface IndexDiffAdded {
export interface IndexDiffAdded<T = DBIndex> {
object: 'index';
type: 'added';
tableId: string;
newIndex: DBIndex;
newIndex: T;
}
export const indexDiffAddedSchema: z.ZodType<IndexDiffAdded> = z.object({
object: z.literal('index'),
type: z.literal('added'),
tableId: z.string(),
newIndex: dbIndexSchema,
});
export const createIndexDiffAddedSchema = <T = DBIndex>(
indexSchema: z.ZodType<T>
): z.ZodType<IndexDiffAdded<T>> => {
return z.object({
object: z.literal('index'),
type: z.literal('added'),
tableId: z.string(),
newIndex: indexSchema,
}) as z.ZodType<IndexDiffAdded<T>>;
};
export interface IndexDiffRemoved {
object: 'index';
@@ -30,9 +33,13 @@ export const indexDiffRemovedSchema: z.ZodType<IndexDiffRemoved> = z.object({
tableId: z.string(),
});
export type IndexDiff = IndexDiffAdded | IndexDiffRemoved;
export type IndexDiff<T = DBIndex> = IndexDiffAdded<T> | IndexDiffRemoved;
export const indexDiffSchema: z.ZodType<IndexDiff> = z.union([
indexDiffAddedSchema,
indexDiffRemovedSchema,
]);
export const createIndexDiffSchema = <T = DBIndex>(
indexSchema: z.ZodType<T>
): z.ZodType<IndexDiff<T>> => {
return z.union([
createIndexDiffAddedSchema(indexSchema),
indexDiffRemovedSchema,
]) as z.ZodType<IndexDiff<T>>;
};

View File

@@ -1,19 +1,21 @@
import { z } from 'zod';
import type { DBRelationship } from '../db-relationship';
import { dbRelationshipSchema } from '../db-relationship';
export interface RelationshipDiffAdded {
export interface RelationshipDiffAdded<T = DBRelationship> {
object: 'relationship';
type: 'added';
newRelationship: DBRelationship;
newRelationship: T;
}
export const relationshipDiffAddedSchema: z.ZodType<RelationshipDiffAdded> =
z.object({
export const createRelationshipDiffAddedSchema = <T = DBRelationship>(
relationshipSchema: z.ZodType<T>
): z.ZodType<RelationshipDiffAdded<T>> => {
return z.object({
object: z.literal('relationship'),
type: z.literal('added'),
newRelationship: dbRelationshipSchema,
});
newRelationship: relationshipSchema,
}) as z.ZodType<RelationshipDiffAdded<T>>;
};
export interface RelationshipDiffRemoved {
object: 'relationship';
@@ -28,9 +30,15 @@ export const relationshipDiffRemovedSchema: z.ZodType<RelationshipDiffRemoved> =
relationshipId: z.string(),
});
export type RelationshipDiff = RelationshipDiffAdded | RelationshipDiffRemoved;
export type RelationshipDiff<T = DBRelationship> =
| RelationshipDiffAdded<T>
| RelationshipDiffRemoved;
export const relationshipDiffSchema: z.ZodType<RelationshipDiff> = z.union([
relationshipDiffAddedSchema,
relationshipDiffRemovedSchema,
]);
export const createRelationshipDiffSchema = <T = DBRelationship>(
relationshipSchema: z.ZodType<T>
): z.ZodType<RelationshipDiff<T>> => {
return z.union([
createRelationshipDiffAddedSchema(relationshipSchema),
relationshipDiffRemovedSchema,
]) as z.ZodType<RelationshipDiff<T>>;
};

View File

@@ -1,6 +1,5 @@
import { z } from 'zod';
import type { DBTable } from '../db-table';
import { dbTableSchema } from '../db-table';
export type TableDiffAttribute = keyof Pick<
DBTable,
@@ -43,22 +42,33 @@ export const TableDiffRemovedSchema: z.ZodType<TableDiffRemoved> = z.object({
tableId: z.string(),
});
export interface TableDiffAdded {
export interface TableDiffAdded<T = DBTable> {
object: 'table';
type: 'added';
tableAdded: DBTable;
tableAdded: T;
}
export const TableDiffAddedSchema: z.ZodType<TableDiffAdded> = z.object({
object: z.literal('table'),
type: z.literal('added'),
tableAdded: dbTableSchema,
});
export const createTableDiffAddedSchema = <T = DBTable>(
tableSchema: z.ZodType<T>
): z.ZodType<TableDiffAdded<T>> => {
return z.object({
object: z.literal('table'),
type: z.literal('added'),
tableAdded: tableSchema,
}) as z.ZodType<TableDiffAdded<T>>;
};
export type TableDiff = TableDiffChanged | TableDiffRemoved | TableDiffAdded;
export type TableDiff<T = DBTable> =
| TableDiffChanged
| TableDiffRemoved
| TableDiffAdded<T>;
export const tableDiffSchema: z.ZodType<TableDiff> = z.union([
TableDiffChangedSchema,
TableDiffRemovedSchema,
TableDiffAddedSchema,
]);
export const createTableDiffSchema = <T = DBTable>(
tableSchema: z.ZodType<T>
): z.ZodType<TableDiff<T>> => {
return z.union([
TableDiffChangedSchema,
TableDiffRemovedSchema,
createTableDiffAddedSchema(tableSchema),
]) as z.ZodType<TableDiff<T>>;
};

13
src/lib/domain/index.ts Normal file
View File

@@ -0,0 +1,13 @@
export * from './area';
export * from './config';
export * from './database-clients';
export * from './database-edition';
export * from './database-type';
export * from './db-custom-type';
export * from './db-dependency';
export * from './db-field';
export * from './db-index';
export * from './db-relationship';
export * from './db-schema';
export * from './db-table';
export * from './diagram';

View File

@@ -7,16 +7,23 @@ export const runningIdGenerator = (): (() => string) => {
return () => (id++).toString();
};
const cloneDiagramWithRunningIds = (diagram: Diagram) =>
cloneDiagram(diagram, { generateId: runningIdGenerator() });
export const cloneDiagramWithRunningIds = (
diagram: Diagram
): { diagram: Diagram; idsMap: Map<string, string> } => {
const { diagram: clonedDiagram, idsMap } = cloneDiagram(diagram, {
generateId: runningIdGenerator(),
});
return { diagram: clonedDiagram, idsMap };
};
const cloneDiagramWithIds = (diagram: Diagram): Diagram => ({
...cloneDiagram(diagram),
...cloneDiagram(diagram).diagram,
id: generateDiagramId(),
});
export const diagramToJSONOutput = (diagram: Diagram): string => {
const clonedDiagram = cloneDiagramWithRunningIds(diagram);
const clonedDiagram = cloneDiagramWithRunningIds(diagram).diagram;
return JSON.stringify(clonedDiagram, null, 2);
};

View File

@@ -141,7 +141,7 @@ export const TableFieldPopover: React.FC<TableFieldPopoverProps> = ({
)}
</Label>
<Textarea
value={localField.comments}
value={localField.comments ?? undefined}
onChange={(e) =>
setLocalField((current) => ({
...current,

View File

@@ -244,7 +244,7 @@ export const TableListItemContent: React.FC<TableListItemContentProps> = ({
</AccordionTrigger>
<AccordionContent className="pb-0 pt-1">
<Textarea
value={table.comments}
value={table.comments ?? undefined}
onChange={(e) =>
updateTable(table.id, {
comments: e.target.value,

View File

@@ -121,7 +121,9 @@ export const TableList: React.FC<TableListProps> = ({ tables }) => {
table1.order !== undefined &&
table2.order !== undefined
) {
return table1.order - table2.order;
return (
(table1.order ?? 0) - (table2.order ?? 0)
);
}
// if both tables don't have order, sort by name

View File

@@ -6,7 +6,7 @@ import { cloneDiagram } from '@/lib/clone';
export const convertTemplateToNewDiagram = (template: Template): Diagram => {
const diagramId = template.diagram.id;
const clonedDiagram = cloneDiagram(template.diagram);
const clonedDiagram: Diagram = cloneDiagram(template.diagram).diagram;
return {
...template.diagram,