Skip to content

Commit

Permalink
fix: circular dependency in schema
Browse files Browse the repository at this point in the history
  • Loading branch information
itsfaqih committed Feb 10, 2023
1 parent fc85fa2 commit 5809837
Show file tree
Hide file tree
Showing 24 changed files with 266 additions and 95 deletions.
3 changes: 2 additions & 1 deletion src/components/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { GeneralPropsPanel } from './GeneralPropsPanel';
import { EditorPropsPanel } from './EditorPropsPanel';
import { ToolbarPanel } from './ToolbarPanel';
import { SimpleFloatingEdge } from './ReactFlow/SimpleFloatingEdge';
import { EdgeType, SchemaType } from '@/schemas/base';
import { useAddCreateTableShortcut } from '@/flow-hooks/useAddCreateTableShortcut';
import { useHandleSaveLocalSchema } from '@/flow-hooks/useHandleSaveLocalSchema';
import { isUuid } from '@/utils/zod';
Expand All @@ -29,6 +28,8 @@ import { UtilsPanel } from './UtilsPanel';
import { AddTableIcon } from './Icon/AddTableIcon';
import { ClipboardIcon } from '@heroicons/react/20/solid';
import { TableWithoutIdType } from '@/schemas/table';
import { SchemaType } from '@/schemas/schema';
import { EdgeType } from '@/schemas/relation';

const nodeTypes: NodeTypes = { table: TableNode } as unknown as NodeTypes;

Expand Down
2 changes: 1 addition & 1 deletion src/components/CreateRelationDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EditorStateContext } from '@/contexts/EditorStateContext';
import { CreateRelationSchema, CreateRelationType, RelationActionEnum } from '@/schemas/base';
import { CreateRelationSchema, CreateRelationType, RelationActionEnum } from '@/schemas/relation';
import * as Accordion from '@radix-ui/react-accordion';
import { zodResolver } from '@hookform/resolvers/zod';
import { useContext, useEffect, useState } from 'react';
Expand Down
2 changes: 1 addition & 1 deletion src/components/EditorPropsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useCopyToClipboard } from 'usehooks-ts';
import { useRouter } from '@tanstack/react-router';
import { EditorPanelContainer } from './EditorPanelContainer';
import { IconButton } from './IconButton';
import { SchemaType } from '@/schemas/base';
import { SchemaType } from '@/schemas/schema';
import { schemaToBase64Url } from '@/utils/schema';
import { useEffect } from 'react';
import { InformationDialog } from './InformationDialog';
Expand Down
2 changes: 1 addition & 1 deletion src/components/GeneralPropsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { IconButton } from './IconButton';
import { useSaveLocalSchema } from '@/mutations/useSaveLocalSchema';
import { EditorPanelContainer } from './EditorPanelContainer';
import { SchemaSchema, SchemaType } from '@/schemas/base';
import { SchemaSchema, SchemaType } from '@/schemas/schema';
import { Textbox } from './Textbox';
import FocusLock from 'react-focus-lock';

Expand Down
2 changes: 1 addition & 1 deletion src/components/ReactFlow/SimpleFloatingEdge.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useReverseRelation } from '@/flow-hooks/useReverseRelation';
import { RelationEdgeType } from '@/schemas/base';
import { RelationEdgeType } from '@/schemas/relation';
import { TableWithoutIdType } from '@/schemas/table';
import { getColumnIdFromHandleId } from '@/utils/reactflow';
import {
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/EditorStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createContext, ReactNode, useMemo, useState } from 'react';
import { useInterpret } from '@xstate/react';
import { copyPasteMachine } from '@/machines/copy-paste-machine';
import { InterpreterFrom } from 'xstate';
import { SchemaType } from '@/schemas/base';
import { SchemaType } from '@/schemas/schema';
import { Edge, useReactFlow } from 'reactflow';
import useUndoable from 'use-undoable';
import { useTransformSchemaToReactFlowData } from '@/flow-hooks/useTransformSchemaToReactFlowData';
Expand Down
84 changes: 84 additions & 0 deletions src/flow-hooks/useCreateColumn.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ReactFlowProvider, useReactFlow } from 'reactflow';
import { renderHook, act } from '@/test/utils';
import { useCreateColumn } from './useCreateColumn';
import { EditorStateProvider } from '@/contexts/EditorStateContext';
import { generateMock } from '@anatine/zod-mock';
import crypto from 'crypto';
import { expect, it } from 'vitest';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { TableSchema, TableWithoutIdType } from '@/schemas/table';
import { CreateVarcharColumnSchema } from '@/schemas/varchar';
import { EdgeType } from '@/schemas/relation';
import { SchemaType, SchemaSchema } from '@/schemas/schema';

const tableId = crypto.randomUUID();

const schemaMock: SchemaType = {
...generateMock(
SchemaSchema.omit({ positions: true, relations: true, tables: true, groups: true }),
),
tables: [
{
...generateMock(TableSchema.omit({ id: true, columns: true, indexes: true })),
id: tableId,
columns: [],
indexes: [],
},
],
groups: [],
relations: [],
positions: [
{
itemId: tableId,
x: 0,
y: 0,
},
],
};

const queryClient = new QueryClient();

queryClient.setQueryData([`schema-${schemaMock.id}`], schemaMock);

it('should create a column', () => {
const { result } = renderHook(
() => {
return {
createColumn: useCreateColumn(),
reactFlowInstance: useReactFlow<TableWithoutIdType, EdgeType>(),
};
},
{
wrapper({ children }) {
return (
<QueryClientProvider client={queryClient}>
<ReactFlowProvider>
<EditorStateProvider schema={schemaMock}>{children}</EditorStateProvider>
</ReactFlowProvider>
</QueryClientProvider>
);
},
},
);

const columnData = generateMock(CreateVarcharColumnSchema);

act(() => {
const { createColumn } = result.current;

createColumn(columnData);
});

expect(
result.current.reactFlowInstance.getNodes().find((node) => node.id === tableId)?.data.columns ??
[],
).toHaveLength(1);
expect(
result.current.reactFlowInstance.getNodes().find((node) => node.id === tableId)?.data.columns[0]
.name,
).toEqual(columnData.name);
expect(
result.current.reactFlowInstance.getNodes().find((node) => node.id === tableId)?.data.columns[0]
.type,
).toEqual(columnData.type);
});
2 changes: 1 addition & 1 deletion src/flow-hooks/useCreateRelation.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useReactFlow } from 'reactflow';
import { CreateRelationType, EdgeType, RelationEdgeType } from '@/schemas/base';
import { useContext } from 'react';
import { EditorStateContext } from '@/contexts/EditorStateContext';
import { useHandleEdgeMarker } from './useHandleEdgeMarker';
import { TableWithoutIdType } from '@/schemas/table';
import { EdgeType, CreateRelationType, RelationEdgeType } from '@/schemas/relation';

export function useCreateRelation() {
const { undoableService } = useContext(EditorStateContext);
Expand Down
3 changes: 2 additions & 1 deletion src/flow-hooks/useHandleEdgeMarker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EdgeType, IndexType } from '@/schemas/base';
import { IndexType } from '@/schemas/base';
import { EdgeType } from '@/schemas/relation';
import { TableWithoutIdType } from '@/schemas/table';
import { getColumnIdFromHandleId } from '@/utils/reactflow';
import { Edge, useReactFlow } from 'reactflow';
Expand Down
4 changes: 3 additions & 1 deletion src/flow-hooks/useHandleSaveLocalSchema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useSaveLocalSchema } from '@/mutations/useSaveLocalSchema';
import { PositionType, RelationType, SchemaType } from '@/schemas/base';
import { PositionType } from '@/schemas/position';
import { RelationType } from '@/schemas/relation';
import { SchemaType } from '@/schemas/schema';
import { TableType } from '@/schemas/table';
import { nodeToTable, nodeToPosition, edgeToRelation } from '@/utils/reactflow';
import { useReactFlow } from 'reactflow';
Expand Down
2 changes: 1 addition & 1 deletion src/flow-hooks/useReverseRelation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useReactFlow } from 'reactflow';
import { EdgeType, RelationType } from '@/schemas/base';
import { useContext } from 'react';
import { EditorStateContext } from '@/contexts/EditorStateContext';
import { TableWithoutIdType } from '@/schemas/table';
import { EdgeType, RelationType } from '@/schemas/relation';

export function useReverseRelation() {
const { undoableService } = useContext(EditorStateContext);
Expand Down
3 changes: 2 additions & 1 deletion src/flow-hooks/useTransformSchemaToReactFlowData.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { z } from 'zod';
import { RelationEdgeType, SchemaSchema } from '@/schemas/base';
import { useHandleEdgeMarker } from './useHandleEdgeMarker';
import { TableNodeType } from '@/schemas/table';
import { RelationEdgeType } from '@/schemas/relation';
import { SchemaSchema } from '@/schemas/schema';

export function useTransformSchemaToReactFlowData() {
const handleEdgeMarker = useHandleEdgeMarker();
Expand Down
2 changes: 1 addition & 1 deletion src/mutations/useSaveLocalSchema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { localSchemaQuery } from '@/queries/useSchemaQuery';
import { SchemaType } from '@/schemas/base';
import { SchemaType } from '@/schemas/schema';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import localforage from 'localforage';

Expand Down
2 changes: 1 addition & 1 deletion src/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SchemaType } from '@/schemas/base';
import { SchemaType } from '@/schemas/schema';
import { base64UrlToSchema } from '@/utils/schema';
import { QueryClient } from '@tanstack/react-query';
import { createRouteConfig, createReactRouter } from '@tanstack/react-router';
Expand Down
3 changes: 1 addition & 2 deletions src/queries/useSchemaQuery.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { SchemaType } from '@/schemas/base';
import { emptySchemaFactory } from '@/utils/schema';
import { SchemaType } from '@/schemas/schema';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
import localforage from 'localforage';
Expand Down
78 changes: 0 additions & 78 deletions src/schemas/base.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { z } from 'zod';
import { Edge } from 'reactflow';
import { ColumnType } from './column';
import { TableSchema } from './table';

export const IndexSchema = z.object({
id: z.string().uuid(),
Expand All @@ -11,40 +8,6 @@ export const IndexSchema = z.object({

export type IndexType = z.infer<typeof IndexSchema>;

export const RelationActionEnum = z.enum(['CASCADE', 'RESTRICT', 'SET_NULL', 'NO_ACTION']);

export const RelationSchema = z.object({
id: z.string().uuid(),
// catch for backward compat
name: z.string().catch('fk'),
source: z.object({
columnId: z.string().uuid(),
tableId: z.string().uuid(),
}),
target: z.object({
columnId: z.string().uuid(),
tableId: z.string().uuid(),
}),
// catch for backward compat
actions: z
.object({
onDelete: RelationActionEnum,
onUpdate: RelationActionEnum,
})
.catch({
onDelete: 'RESTRICT',
onUpdate: 'RESTRICT',
}),
});

export type RelationType = z.infer<typeof RelationSchema>;

export const CreateRelationSchema = RelationSchema.omit({
id: true,
});

export type CreateRelationType = z.infer<typeof CreateRelationSchema>;

export const VisibilityAttribute = z.union([z.literal('INVISIBLE'), z.literal('VISIBLE')]);
export const NullabilityAttribute = z.union([z.literal('NULLABLE'), z.literal('NOT_NULL')]);
export const SignabilityAttribute = z.union([z.literal('UNSIGNED'), z.literal('SIGNED')]);
Expand All @@ -68,44 +31,3 @@ export const BaseUpdateColumnSchema = BaseColumnSchema.extend({
isUniqueIndex: z.boolean(),
tableId: z.string().uuid(),
});

export type RelationEdgeType = Edge<EdgeType>;

export const GroupSchema = z.object({
id: z.string().uuid(),
name: z.string(),
width: z.number().int(),
height: z.number().int(),
color: z.object({
background: z.string(),
border: z.string(),
}),
});

export const PositionSchema = z.object({
itemId: z.string().uuid(),
groupId: z.string().uuid().optional(),
x: z.number(),
y: z.number(),
});

export type PositionType = z.infer<typeof PositionSchema>;

export const SchemaSchema = z.object({
id: z.string().uuid(),
name: z.string().trim().max(64),
vendor: z.enum(['MYSQL:8.0']),
tables: TableSchema.array(),
groups: GroupSchema.array(),
positions: PositionSchema.array(),
relations: RelationSchema.array(),
});

export type SchemaType = z.infer<typeof SchemaSchema>;

export type EdgeType = {
name: RelationType['name'];
sourceColumnId: ColumnType['id'];
targetColumnId: ColumnType['id'];
actions: RelationType['actions'];
};
12 changes: 12 additions & 0 deletions src/schemas/group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { z } from 'zod';

export const GroupSchema = z.object({
id: z.string().uuid(),
name: z.string(),
width: z.number().int(),
height: z.number().int(),
color: z.object({
background: z.string(),
border: z.string(),
}),
});
10 changes: 10 additions & 0 deletions src/schemas/position.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { z } from 'zod';

export const PositionSchema = z.object({
itemId: z.string().uuid(),
groupId: z.string().uuid().optional(),
x: z.number(),
y: z.number(),
});

export type PositionType = z.infer<typeof PositionSchema>;
46 changes: 46 additions & 0 deletions src/schemas/relation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Edge } from 'reactflow';
import { z } from 'zod';
import { ColumnType } from './column';

export const RelationActionEnum = z.enum(['CASCADE', 'RESTRICT', 'SET_NULL', 'NO_ACTION']);

export const RelationSchema = z.object({
id: z.string().uuid(),
// catch for backward compat
name: z.string().catch('fk'),
source: z.object({
columnId: z.string().uuid(),
tableId: z.string().uuid(),
}),
target: z.object({
columnId: z.string().uuid(),
tableId: z.string().uuid(),
}),
// catch for backward compat
actions: z
.object({
onDelete: RelationActionEnum,
onUpdate: RelationActionEnum,
})
.catch({
onDelete: 'RESTRICT',
onUpdate: 'RESTRICT',
}),
});

export type RelationType = z.infer<typeof RelationSchema>;

export const CreateRelationSchema = RelationSchema.omit({
id: true,
});

export type CreateRelationType = z.infer<typeof CreateRelationSchema>;

export type EdgeType = {
name: RelationType['name'];
sourceColumnId: ColumnType['id'];
targetColumnId: ColumnType['id'];
actions: RelationType['actions'];
};

export type RelationEdgeType = Edge<EdgeType>;
17 changes: 17 additions & 0 deletions src/schemas/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { z } from 'zod';
import { GroupSchema } from './group';
import { PositionSchema } from './position';
import { RelationSchema } from './relation';
import { TableSchema } from './table';

export const SchemaSchema = z.object({
id: z.string().uuid(),
name: z.string().trim().max(64),
vendor: z.enum(['MYSQL:8.0']),
tables: TableSchema.array(),
groups: GroupSchema.array(),
positions: PositionSchema.array(),
relations: RelationSchema.array(),
});

export type SchemaType = z.infer<typeof SchemaSchema>;
Loading

0 comments on commit 5809837

Please sign in to comment.