@rebasepro/studio 0.8.0 → 0.9.0
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/README.md +1 -1
- package/dist/{JSEditor-Ca4XYGRp.js → JSEditor-Sgmw1SHC.js} +25 -29
- package/dist/JSEditor-Sgmw1SHC.js.map +1 -0
- package/dist/{RLSEditor-BM64laoW.js → RLSEditor-DPhky8XW.js} +8 -18
- package/dist/RLSEditor-DPhky8XW.js.map +1 -0
- package/dist/{SQLEditor-CuAhR-zr.js → SQLEditor-CgR2hlUI.js} +5 -5
- package/dist/SQLEditor-CgR2hlUI.js.map +1 -0
- package/dist/{SchemaVisualizer-OibKoD3g.js → SchemaVisualizer-WK4ZZR5X.js} +9 -9
- package/dist/SchemaVisualizer-WK4ZZR5X.js.map +1 -0
- package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +6 -6
- package/dist/index.umd.js +41 -55
- package/dist/index.umd.js.map +1 -1
- package/dist/utils/pgColumnToProperty.d.ts +3 -3
- package/dist/utils/sql_utils.d.ts +3 -3
- package/package.json +8 -8
- package/src/components/JSEditor/JSEditor.tsx +7 -7
- package/src/components/JSEditor/JSMonacoEditor.tsx +20 -24
- package/src/components/RLSEditor/RLSEditor.tsx +18 -31
- package/src/components/SQLEditor/SQLEditor.tsx +4 -4
- package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +7 -7
- package/src/components/SchemaVisualizer/useSchemaGraph.ts +11 -11
- package/src/index.ts +1 -1
- package/src/utils/pgColumnToProperty.ts +3 -3
- package/src/utils/sql_utils.test.ts +6 -6
- package/src/utils/sql_utils.ts +3 -3
- package/dist/JSEditor-Ca4XYGRp.js.map +0 -1
- package/dist/RLSEditor-BM64laoW.js.map +0 -1
- package/dist/SQLEditor-CuAhR-zr.js.map +0 -1
- package/dist/SchemaVisualizer-OibKoD3g.js.map +0 -1
- package/src/vite-env.d.ts +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SchemaVisualizer-WK4ZZR5X.js","names":[],"sources":["../src/components/SchemaVisualizer/schema-visualizer.utils.ts","../src/components/SchemaVisualizer/useSchemaGraph.ts","../src/components/SchemaVisualizer/TableNode.tsx","../src/components/SchemaVisualizer/RelationEdge.tsx","../src/components/SchemaVisualizer/SchemaVisualizer.tsx"],"sourcesContent":["import dagre from \"dagre\";\nimport type { Node, Edge } from \"@xyflow/react\";\n\n// ─── Layout Constants ─────────────────────────────────────────────────\nexport const NODE_WIDTH = 280;\n/** Header with a single line of text (junction tables or tableName === collectionName). */\nconst HEADER_HEIGHT_SINGLE = 33;\n/** Header with two lines (name + subtitle when collectionName !== tableName). */\nconst HEADER_HEIGHT_DOUBLE = 47;\nconst ROW_HEIGHT = 28; // height per column row\n\n/**\n * Compute the correct header height for a table node.\n */\nexport const getHeaderHeight = (opts: {\n isJunction: boolean;\n collectionName: string;\n tableName: string;\n}): number => {\n if (opts.isJunction) return HEADER_HEIGHT_SINGLE;\n return opts.collectionName !== opts.tableName\n ? HEADER_HEIGHT_DOUBLE\n : HEADER_HEIGHT_SINGLE;\n};\n\n/**\n * Estimate the pixel height of a table node based on column count.\n */\nconst estimateNodeHeight = (columnCount: number, headerHeight: number = HEADER_HEIGHT_DOUBLE): number =>\n headerHeight + Math.max(columnCount, 1) * ROW_HEIGHT + 4; // +4 for bottom padding\n\n/**\n * Get the vertical center Y of a specific column row (0-indexed)\n * relative to the top of the node.\n */\nexport const getColumnRowY = (rowIndex: number, headerHeight: number = HEADER_HEIGHT_DOUBLE): number =>\n headerHeight + rowIndex * ROW_HEIGHT + ROW_HEIGHT / 2;\n\n// ─── Auto-Layout via Dagre ────────────────────────────────────────────\n\nexport type LayoutDirection = \"TB\" | \"LR\";\n\n/**\n * Compute node positions using the dagre graph layout engine.\n * Returns a new array of nodes with updated `position`.\n */\nexport const getLayoutedElements = (\n nodes: Node[],\n edges: Edge[],\n direction: LayoutDirection = \"LR\"\n): { nodes: Node[]; edges: Edge[] } => {\n const g = new dagre.graphlib.Graph();\n g.setGraph({\n rankdir: direction,\n nodesep: 100,\n ranksep: 180,\n edgesep: 60,\n marginx: 60,\n marginy: 60\n });\n g.setDefaultEdgeLabel(() => ({}));\n\n const nodeHeights = new Map<string, number>();\n\n nodes.forEach((node) => {\n const data = node.data as { columns?: unknown[]; isJunction?: boolean; collectionName?: string; tableName?: string };\n const columnCount = data.columns?.length ?? 3;\n const headerH = getHeaderHeight({\n isJunction: Boolean(data.isJunction),\n collectionName: data.collectionName ?? \"\",\n tableName: data.tableName ?? \"\"\n });\n const h = estimateNodeHeight(columnCount, headerH);\n nodeHeights.set(node.id, h);\n g.setNode(node.id, {\n width: NODE_WIDTH,\n height: h\n });\n });\n\n edges.forEach((edge) => {\n g.setEdge(edge.source, edge.target);\n });\n\n dagre.layout(g);\n\n const layoutedNodes = nodes.map((node) => {\n const nodeWithPosition = g.node(node.id);\n const h = nodeHeights.get(node.id) ?? estimateNodeHeight(3);\n return {\n ...node,\n data: {\n ...node.data,\n layoutDirection: direction\n },\n position: {\n x: nodeWithPosition.x - NODE_WIDTH / 2,\n y: nodeWithPosition.y - h / 2\n }\n };\n });\n\n return { nodes: layoutedNodes,\nedges };\n};\n\n// ─── Column type → display label ──────────────────────────────────────\n\nconst TYPE_LABELS: Record<string, string> = {\n string: \"varchar\",\n number: \"integer\",\n boolean: \"boolean\",\n date: \"timestamp\",\n map: \"jsonb\",\n array: \"jsonb\",\n relation: \"FK\"\n};\n\nexport const getTypeLabel = (type: string): string =>\n TYPE_LABELS[type] ?? type;\n\n// ─── Edge styling by relation type ────────────────────────────────────\n\nexport interface RelationEdgeData {\n cardinality: \"one\" | \"many\";\n direction: \"owning\" | \"inverse\";\n relationName: string;\n hasJunction: boolean;\n hasJoinPath: boolean;\n label: string;\n}\n\nexport const getCardinalityLabel = (\n cardinality: \"one\" | \"many\",\n direction: \"owning\" | \"inverse\"\n): string => {\n if (cardinality === \"many\") return \"M:N\";\n if (direction === \"inverse\") return \"1:1 ←\";\n return \"N:1\";\n};\n","import { useMemo, useState, useCallback, useEffect } from \"react\";\nimport type { Node, Edge } from \"@xyflow/react\";\nimport { MarkerType } from \"@xyflow/react\";\nimport { isPostgresCollectionConfig } from \"@rebasepro/types\";\nimport type { CollectionConfig, PostgresCollectionConfig, Relation } from \"@rebasepro/types\";\nimport { resolveCollectionRelations } from \"@rebasepro/common\";\nimport { getLayoutedElements, getCardinalityLabel, getTypeLabel, NODE_WIDTH } from \"./schema-visualizer.utils\";\nimport type { LayoutDirection, RelationEdgeData } from \"./schema-visualizer.utils\";\n\n// ─── Column info extracted from a collection ──────────────────────────\n\nexport interface ColumnInfo {\n name: string;\n type: string;\n typeLabel: string;\n isPrimaryKey: boolean;\n isForeignKey: boolean;\n isRequired: boolean;\n isEnum: boolean;\n enumValues?: string[];\n relationName?: string;\n}\n\n// ─── Node data shape ──────────────────────────────────────────────────\n\nexport interface TableNodeData {\n tableName: string;\n collectionName: string;\n slug: string;\n columns: ColumnInfo[];\n isJunction: boolean;\n isUnmanaged: boolean;\n rlsEnabled: boolean;\n historyEnabled: boolean;\n icon?: string;\n [key: string]: unknown;\n}\n\n// ─── Extract columns from a CollectionConfig ─────────────────────────\n\nconst extractColumns = (collection: CollectionConfig): ColumnInfo[] => {\n const columns: ColumnInfo[] = [];\n const properties = collection.properties ?? {};\n\n for (const [propName, prop] of Object.entries(properties)) {\n if (prop.type === \"relation\") continue; // Relations are shown as edges, not columns\n\n const isPk =\n (\"isId\" in prop && Boolean(prop.isId)) ||\n (!Object.values(properties).some(\n (p) => \"isId\" in p && Boolean(p.isId)\n ) &&\n propName === \"id\");\n\n const isEnum = prop.type === \"string\" && \"enum\" in prop && Boolean(prop.enum);\n\n let enumValues: string[] | undefined;\n if (isEnum && \"enum\" in prop && prop.enum) {\n const enumProp = prop.enum;\n if (Array.isArray(enumProp)) {\n enumValues = enumProp.map((v: unknown) =>\n typeof v === \"string\" ? v : String((v as Record<string, unknown>)?.id ?? v)\n );\n } else if (typeof enumProp === \"object\" && enumProp !== null) {\n enumValues = Object.keys(enumProp);\n }\n }\n\n columns.push({\n name: propName,\n type: prop.type,\n typeLabel: getTypeLabel(prop.type),\n isPrimaryKey: isPk,\n isForeignKey: false,\n isRequired: Boolean(prop.validation?.required),\n isEnum,\n enumValues\n });\n }\n\n // Add FK columns from owning relations\n if (isPostgresCollectionConfig(collection)) {\n try {\n const resolvedRelations = resolveCollectionRelations(collection);\n for (const rel of Object.values(resolvedRelations)) {\n if (rel.direction === \"owning\" && rel.cardinality === \"one\" && rel.localKey) {\n // Only add if not already present as a regular column\n if (!columns.some((c) => c.name === rel.localKey)) {\n columns.push({\n name: rel.localKey,\n type: \"number\",\n typeLabel: \"FK\",\n isPrimaryKey: false,\n isForeignKey: true,\n isRequired: false,\n isEnum: false,\n relationName: rel.relationName\n });\n } else {\n // Mark existing column as FK\n const existing = columns.find((c) => c.name === rel.localKey);\n if (existing) {\n existing.isForeignKey = true;\n existing.relationName = rel.relationName;\n }\n }\n }\n }\n } catch {\n // Ignore resolution errors\n }\n }\n\n return columns;\n};\n\n// ─── Build graph from collections ─────────────────────────────────────\n\nconst buildGraph = (\n collections: CollectionConfig[],\n direction: LayoutDirection\n): { nodes: Node[]; edges: Edge[] } => {\n const nodes: Node[] = [];\n const edges: Edge[] = [];\n const tableToNodeId = new Map<string, string>();\n // Track columns per node so we can resolve handle IDs for edges\n const nodeColumns = new Map<string, ColumnInfo[]>();\n\n // Helper: find the PK column name for a node (falls back to \"id\")\n const getPkHandle = (nodeId: string): string => {\n const cols = nodeColumns.get(nodeId);\n const pk = cols?.find((c) => c.isPrimaryKey);\n return pk ? `target-${pk.name}` : \"target-default\";\n };\n\n // Helper: find the FK column name for a node pointing to a relation\n const getFkHandle = (nodeId: string, localKey: string | undefined): string => {\n if (!localKey) return \"source-default\";\n const cols = nodeColumns.get(nodeId);\n const fk = cols?.find((c) => c.name === localKey);\n return fk ? `source-${fk.name}` : \"source-default\";\n };\n\n // 1. Create nodes for each collection\n for (const collection of collections) {\n if (!isPostgresCollectionConfig(collection)) continue;\n\n const tableName = collection.table ?? collection.slug;\n const nodeId = `table-${tableName}`;\n tableToNodeId.set(tableName, nodeId);\n\n const columns = extractColumns(collection);\n nodeColumns.set(nodeId, columns);\n\n const nodeData: TableNodeData = {\n tableName,\n collectionName: collection.name,\n slug: collection.slug,\n columns,\n isJunction: false,\n isUnmanaged: false,\n rlsEnabled: Boolean(collection.securityRules && collection.securityRules.length > 0),\n historyEnabled: Boolean(collection.history),\n icon: typeof collection.icon === \"string\" ? collection.icon : undefined\n };\n\n nodes.push({\n id: nodeId,\n type: \"tableNode\",\n position: { x: 0,\ny: 0 },\n data: nodeData\n });\n }\n\n // 2. Create junction table nodes and edges\n const processedJunctions = new Set<string>();\n\n for (const collection of collections) {\n if (!isPostgresCollectionConfig(collection)) continue;\n\n const tableName = collection.table ?? collection.slug;\n const sourceNodeId = tableToNodeId.get(tableName);\n if (!sourceNodeId) continue;\n\n let resolvedRelations: Record<string, Relation>;\n try {\n resolvedRelations = resolveCollectionRelations(collection);\n } catch {\n continue;\n }\n\n for (const [relationKey, rel] of Object.entries(resolvedRelations)) {\n let targetCollection: CollectionConfig;\n try {\n targetCollection = rel.target();\n } catch {\n continue;\n }\n\n if (!isPostgresCollectionConfig(targetCollection)) continue;\n\n const targetTable = targetCollection.table ?? targetCollection.slug;\n const targetNodeId = tableToNodeId.get(targetTable);\n if (!targetNodeId) continue;\n\n // Skip inverse relations (we only draw owning ones)\n if (rel.direction === \"inverse\") continue;\n\n const edgeData: RelationEdgeData = {\n cardinality: rel.cardinality,\n direction: rel.direction ?? \"owning\",\n relationName: rel.relationName ?? relationKey,\n hasJunction: Boolean(rel.through),\n hasJoinPath: Boolean(rel.joinPath),\n label: getCardinalityLabel(rel.cardinality, rel.direction ?? \"owning\")\n };\n\n if (rel.through && !processedJunctions.has(rel.through.table)) {\n // Many-to-many: create junction node + two edges\n processedJunctions.add(rel.through.table);\n const junctionNodeId = `junction-${rel.through.table}`;\n\n const junctionColumns: ColumnInfo[] = [\n {\n name: rel.through.sourceColumn,\n type: \"number\",\n typeLabel: \"FK\",\n isPrimaryKey: true,\n isForeignKey: true,\n isRequired: true,\n isEnum: false\n },\n {\n name: rel.through.targetColumn,\n type: \"number\",\n typeLabel: \"FK\",\n isPrimaryKey: true,\n isForeignKey: true,\n isRequired: true,\n isEnum: false\n }\n ];\n nodeColumns.set(junctionNodeId, junctionColumns);\n\n nodes.push({\n id: junctionNodeId,\n type: \"tableNode\",\n position: { x: 0,\ny: 0 },\n data: {\n tableName: rel.through.table,\n collectionName: rel.through.table,\n slug: rel.through.table,\n columns: junctionColumns,\n isJunction: true,\n isUnmanaged: false,\n rlsEnabled: false,\n historyEnabled: false\n } satisfies TableNodeData\n });\n\n // Source table → junction (junction references source PK)\n const sourcePk = nodeColumns.get(sourceNodeId)?.find((c) => c.isPrimaryKey);\n edges.push({\n id: `edge-${sourceNodeId}-${junctionNodeId}`,\n source: sourceNodeId,\n target: junctionNodeId,\n sourceHandle: sourcePk ? `source-${sourcePk.name}` : \"source-default\",\n targetHandle: `target-${rel.through.sourceColumn}`,\n type: \"relationEdge\",\n data: { ...edgeData,\nlabel: \"1:N\" } as Record<string, unknown>,\n markerEnd: { type: MarkerType.ArrowClosed,\nwidth: 16,\nheight: 16 }\n });\n\n // Junction → target table\n edges.push({\n id: `edge-${junctionNodeId}-${targetNodeId}`,\n source: junctionNodeId,\n target: targetNodeId,\n sourceHandle: `source-${rel.through.targetColumn}`,\n targetHandle: getPkHandle(targetNodeId),\n type: \"relationEdge\",\n data: { ...edgeData,\nlabel: \"N:1\" } as Record<string, unknown>,\n markerEnd: { type: MarkerType.ArrowClosed,\nwidth: 16,\nheight: 16 }\n });\n } else if (!rel.through) {\n // Direct relation (one-to-one or many-to-one)\n edges.push({\n id: `edge-${sourceNodeId}-${targetNodeId}-${relationKey}`,\n source: sourceNodeId,\n target: targetNodeId,\n sourceHandle: getFkHandle(sourceNodeId, rel.localKey),\n targetHandle: getPkHandle(targetNodeId),\n type: \"relationEdge\",\n data: { ...edgeData } as Record<string, unknown>,\n markerEnd: { type: MarkerType.ArrowClosed,\nwidth: 16,\nheight: 16 }\n });\n }\n }\n }\n // 3. Apply dagre layout first to get node positions\n const layoutResult = getLayoutedElements(nodes, edges, direction);\n\n // 4. Build a position lookup from the laid-out nodes\n const nodePositions = new Map<string, { x: number }>();\n for (const node of layoutResult.nodes) {\n nodePositions.set(node.id, { x: node.position.x });\n }\n\n // 5. Resolve handle sides based on relative node positions.\n // If source is left of target → source exits Right, target enters Left.\n // If source is right of target → source exits Left, target enters Right.\n for (const edge of layoutResult.edges) {\n const srcPos = nodePositions.get(edge.source);\n const tgtPos = nodePositions.get(edge.target);\n\n const srcBaseHandle = (edge.sourceHandle ?? \"source-default\") as string;\n const tgtBaseHandle = (edge.targetHandle ?? \"target-default\") as string;\n\n if (srcPos && tgtPos) {\n const srcCenterX = srcPos.x + NODE_WIDTH / 2;\n const tgtCenterX = tgtPos.x + NODE_WIDTH / 2;\n const sourceIsLeft = srcCenterX <= tgtCenterX;\n\n edge.sourceHandle = `${srcBaseHandle}-${sourceIsLeft ? \"right\" : \"left\"}`;\n edge.targetHandle = `${tgtBaseHandle}-${sourceIsLeft ? \"left\" : \"right\"}`;\n } else {\n edge.sourceHandle = `${srcBaseHandle}-right`;\n edge.targetHandle = `${tgtBaseHandle}-left`;\n }\n }\n\n return layoutResult;\n};\n\n// ─── Hook ─────────────────────────────────────────────────────────────\n\nexport interface UseSchemaGraphResult {\n nodes: Node[];\n edges: Edge[];\n direction: LayoutDirection;\n setDirection: (dir: LayoutDirection) => void;\n relayout: () => void;\n isLoading: boolean;\n tableCount: number;\n relationCount: number;\n}\n\nexport const useSchemaGraph = (\n collections: CollectionConfig[] | undefined\n): UseSchemaGraphResult => {\n const [direction, setDirection] = useState<LayoutDirection>(\"LR\");\n const [version, setVersion] = useState(0);\n\n const relayout = useCallback(() => setVersion((v) => v + 1), []);\n\n // Re-layout on direction change\n useEffect(() => {\n setVersion((v) => v + 1);\n }, [direction]);\n\n const { nodes, edges, tableCount, relationCount } = useMemo(() => {\n if (!collections || collections.length === 0) {\n return { nodes: [],\nedges: [],\ntableCount: 0,\nrelationCount: 0 };\n }\n const result = buildGraph(collections, direction);\n return {\n ...result,\n tableCount: result.nodes.length,\n relationCount: result.edges.length\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [collections, direction, version]);\n\n return {\n nodes,\n edges,\n direction,\n setDirection,\n relayout,\n isLoading: !collections,\n tableCount,\n relationCount\n };\n};\n","import React, { memo, useMemo } from \"react\";\nimport { Handle, Position } from \"@xyflow/react\";\nimport type { NodeProps } from \"@xyflow/react\";\nimport { Typography, Chip, Tooltip, cls } from \"@rebasepro/ui\";\nimport { IconForView } from \"@rebasepro/core\";\nimport type { TableNodeData, ColumnInfo } from \"./useSchemaGraph\";\nimport { getColumnRowY, getHeaderHeight } from \"./schema-visualizer.utils\";\n\n/**\n * Custom React Flow node that renders a database table as a card.\n *\n * Every PK and FK column renders handles on BOTH Left and Right sides.\n * The edge builder picks which side to use based on relative node position.\n */\nconst TableNodeInner = ({ data, selected }: NodeProps) => {\n const {\n tableName,\n collectionName,\n columns,\n isJunction,\n rlsEnabled,\n historyEnabled,\n icon\n } = data as TableNodeData;\n\n // Build handles: every PK/FK column gets Left + Right handles\n const handles = useMemo(() => {\n const result: { id: string; type: \"source\" | \"target\"; position: Position; top: number }[] = [];\n const cols = columns as ColumnInfo[];\n const headerH = getHeaderHeight({\n isJunction: Boolean(isJunction),\n collectionName: collectionName as string,\n tableName: tableName as string\n });\n const midY = cols.length > 0 ? getColumnRowY(Math.floor(cols.length / 2), headerH) : 30;\n\n cols.forEach((col, idx) => {\n const y = getColumnRowY(idx, headerH);\n\n if (col.isForeignKey && !col.isPrimaryKey) {\n // FK: source handles on both sides\n result.push({ id: `source-${col.name}-right`,\ntype: \"source\",\nposition: Position.Right,\ntop: y });\n result.push({ id: `source-${col.name}-left`,\ntype: \"source\",\nposition: Position.Left,\ntop: y });\n }\n if (col.isPrimaryKey) {\n // PK: target + source handles on both sides\n result.push({ id: `target-${col.name}-right`,\ntype: \"target\",\nposition: Position.Right,\ntop: y });\n result.push({ id: `target-${col.name}-left`,\ntype: \"target\",\nposition: Position.Left,\ntop: y });\n result.push({ id: `source-${col.name}-right`,\ntype: \"source\",\nposition: Position.Right,\ntop: y });\n result.push({ id: `source-${col.name}-left`,\ntype: \"source\",\nposition: Position.Left,\ntop: y });\n }\n });\n\n // Default handles on both sides\n result.push({ id: \"target-default-right\",\ntype: \"target\",\nposition: Position.Right,\ntop: midY });\n result.push({ id: \"target-default-left\",\ntype: \"target\",\nposition: Position.Left,\ntop: midY });\n result.push({ id: \"source-default-right\",\ntype: \"source\",\nposition: Position.Right,\ntop: midY });\n result.push({ id: \"source-default-left\",\ntype: \"source\",\nposition: Position.Left,\ntop: midY });\n\n return result;\n }, [columns, isJunction, collectionName, tableName]);\n\n return (\n <div\n className={cls(\n \"relative rounded-lg border bg-white dark:bg-surface-900 shadow-sm transition-all duration-200 min-w-[240px] max-w-[320px]\",\n selected\n ? \"border-primary ring-2 ring-primary/20 shadow-md\"\n : \"border-surface-200/40 dark:border-surface-700/40 hover:shadow-md hover:border-surface-300 dark:hover:border-surface-600\",\n isJunction && \"border-dashed\"\n )}\n >\n {/* ── Header ── */}\n <div\n className={cls(\n \"flex items-center gap-2 px-3 py-2 border-b rounded-t-lg\",\n isJunction\n ? \"bg-surface-50 dark:bg-surface-950/50 border-surface-200/30 dark:border-surface-700/30\"\n : \"bg-surface-50 dark:bg-surface-950 border-surface-200/40 dark:border-surface-700/40\"\n )}\n >\n {icon && !isJunction && (\n <div className=\"text-primary shrink-0\">\n <IconForView\n collectionOrView={{ slug: tableName,\nname: collectionName,\nicon }}\n size=\"smallest\"\n />\n </div>\n )}\n {isJunction && (\n <svg\n className=\"w-3.5 h-3.5 text-text-disabled dark:text-text-disabled-dark shrink-0\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4\"\n />\n </svg>\n )}\n <div className=\"flex flex-col min-w-0\">\n <Typography\n variant=\"caption\"\n className={cls(\n \"font-semibold truncate text-[12px]\",\n isJunction\n ? \"text-text-secondary dark:text-text-secondary-dark\"\n : \"text-text-primary dark:text-text-primary-dark\"\n )}\n >\n {tableName}\n </Typography>\n {collectionName !== tableName && !isJunction && (\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark truncate\"\n >\n {collectionName}\n </Typography>\n )}\n </div>\n\n {/* Badges */}\n <div className=\"ml-auto flex items-center gap-1 shrink-0\">\n {rlsEnabled && (\n <Tooltip title=\"RLS enabled\">\n <div className=\"w-1.5 h-1.5 rounded-full bg-green-500\"/>\n </Tooltip>\n )}\n {historyEnabled && (\n <Tooltip title=\"History enabled\">\n <div className=\"w-1.5 h-1.5 rounded-full bg-blue-400\"/>\n </Tooltip>\n )}\n </div>\n </div>\n\n {/* ── Columns ── */}\n <div className=\"divide-y divide-surface-100 dark:divide-surface-950/60\">\n {(columns as ColumnInfo[]).map((col: ColumnInfo) => (\n <div\n key={col.name}\n className={cls(\n \"flex items-center gap-2 px-3 py-1.5 text-xs transition-colors\",\n col.isPrimaryKey && \"bg-amber-50/50 dark:bg-amber-950/10\",\n col.isForeignKey &&\n !col.isPrimaryKey &&\n \"bg-blue-50/40 dark:bg-blue-950/10\"\n )}\n >\n <span className=\"w-3 shrink-0 text-center\">\n {col.isPrimaryKey && (\n <Tooltip title=\"Primary Key\">\n <span className=\"text-amber-500 text-[10px] font-bold\">🔑</span>\n </Tooltip>\n )}\n {col.isForeignKey && !col.isPrimaryKey && (\n <Tooltip title={`FK → ${col.relationName ?? \"?\"}`}>\n <span className=\"text-blue-400 text-[10px] font-bold\">🔗</span>\n </Tooltip>\n )}\n </span>\n\n <Typography\n variant=\"caption\"\n className={cls(\n \"font-mono text-[11px] truncate flex-1 min-w-0\",\n col.isPrimaryKey\n ? \"font-semibold text-amber-700 dark:text-amber-400\"\n : col.isForeignKey\n ? \"text-blue-600 dark:text-blue-400\"\n : \"text-text-primary dark:text-text-primary-dark\"\n )}\n >\n {col.name}\n </Typography>\n\n {col.isEnum ? (\n <Chip\n size=\"smallest\"\n className=\"bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400 border-violet-200 dark:border-violet-800 text-[9px] py-0\"\n >\n enum\n </Chip>\n ) : (\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark font-mono shrink-0\"\n >\n {col.typeLabel}\n </Typography>\n )}\n\n {col.isRequired && !col.isPrimaryKey && (\n <Tooltip title=\"Required\">\n <span className=\"text-red-400 text-[9px]\">•</span>\n </Tooltip>\n )}\n </div>\n ))}\n </div>\n\n {/* ── Handles at node root ── */}\n {handles.map((h) => (\n <Handle\n key={h.id}\n id={h.id}\n type={h.type}\n position={h.position}\n style={{ top: h.top }}\n className={cls(\n \"!w-2 !h-2 !border-2 !border-white dark:!border-surface-900\",\n h.type === \"source\" ? \"!bg-blue-400\" : \"!bg-amber-400\"\n )}\n />\n ))}\n </div>\n );\n};\n\nexport const TableNode = memo(TableNodeInner);\n","import React, { memo } from \"react\";\nimport { BaseEdge, getSmoothStepPath, EdgeLabelRenderer } from \"@xyflow/react\";\nimport type { EdgeProps } from \"@xyflow/react\";\nimport { cls } from \"@rebasepro/ui\";\nimport type { RelationEdgeData } from \"./schema-visualizer.utils\";\n\n/**\n * Custom React Flow edge that renders relations with style variations\n * based on cardinality and direction.\n *\n * - Owning one-to-one: solid, primary color\n * - Many-to-many (junction): solid, violet\n * - Inverse: dashed, muted\n * - Join path: dotted, muted\n */\nconst RelationEdgeInner = ({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n data,\n selected\n}: EdgeProps) => {\n const edgeData = data as RelationEdgeData | undefined;\n\n const [edgePath, labelX, labelY] = getSmoothStepPath({\n sourceX,\n sourceY,\n sourcePosition,\n targetX,\n targetY,\n targetPosition,\n borderRadius: 8\n });\n\n const isInverse = edgeData?.direction === \"inverse\";\n const isJoinPath = edgeData?.hasJoinPath;\n const isJunction = edgeData?.hasJunction;\n\n // Determine stroke style\n let strokeColor = \"var(--rf-edge-stroke, #94a3b8)\";\n let strokeDasharray: string | undefined;\n let strokeWidth = 1.5;\n\n if (selected) {\n strokeColor = \"var(--rf-edge-stroke-selected, #6366f1)\";\n strokeWidth = 2.5;\n } else if (isJunction) {\n strokeColor = \"#8b5cf6\"; // violet\n } else if (isInverse) {\n strokeDasharray = \"6 3\";\n strokeColor = \"#94a3b8\";\n } else if (isJoinPath) {\n strokeDasharray = \"3 3\";\n strokeColor = \"#94a3b8\";\n } else {\n strokeColor = \"#6366f1\"; // primary/indigo\n }\n\n return (\n <>\n <BaseEdge\n id={id}\n path={edgePath}\n style={{\n stroke: strokeColor,\n strokeWidth,\n strokeDasharray\n }}\n />\n {edgeData?.label && (\n <EdgeLabelRenderer>\n <div\n style={{\n position: \"absolute\",\n transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,\n pointerEvents: \"all\"\n }}\n className={cls(\n \"px-1.5 py-0.5 rounded text-[9px] font-mono font-semibold leading-none\",\n \"bg-white dark:bg-surface-900 border\",\n selected\n ? \"border-primary text-primary\"\n : isJunction\n ? \"border-violet-200 dark:border-violet-800 text-violet-600 dark:text-violet-400\"\n : isInverse || isJoinPath\n ? \"border-surface-200 dark:border-surface-700 text-text-disabled dark:text-text-disabled-dark\"\n : \"border-primary/30 dark:border-primary/30 text-primary\"\n )}\n >\n {edgeData.label}\n </div>\n </EdgeLabelRenderer>\n )}\n </>\n );\n};\n\nexport const RelationEdge = memo(RelationEdgeInner);\n","import React, { useState, useCallback, useMemo, useEffect, useRef } from \"react\";\nimport {\n ReactFlow,\n Controls,\n MiniMap,\n Background,\n BackgroundVariant,\n useReactFlow,\n ReactFlowProvider,\n applyNodeChanges,\n applyEdgeChanges\n} from \"@xyflow/react\";\nimport type { Node, Edge, NodeChange, EdgeChange } from \"@xyflow/react\";\nimport \"@xyflow/react/dist/style.css\";\nimport {\n Button,\n SearchBar,\n TextField,\n Tooltip,\n Alert,\n Typography,\n cls,\n defaultBorderMixin,\n CircularProgress,\n ResizablePanels,\n IconButton\n} from \"@rebasepro/ui\";\nimport {\n useStudioCollectionRegistry,\n IconForView\n} from \"@rebasepro/core\";\nimport type { CollectionConfig } from \"@rebasepro/types\";\nimport { isPostgresCollectionConfig } from \"@rebasepro/types\";\nimport { useSchemaGraph } from \"./useSchemaGraph\";\nimport type { TableNodeData } from \"./useSchemaGraph\";\nimport { TableNode } from \"./TableNode\";\nimport { RelationEdge } from \"./RelationEdge\";\n\n\n// ─── Custom node / edge type registrations ────────────────────────────\n\nconst nodeTypes = {\n tableNode: TableNode\n};\n\nconst edgeTypes = {\n relationEdge: RelationEdge\n};\n\n// ─── Inner component (needs ReactFlowProvider) ─────────────────────────\n\nfunction SchemaVisualizerCanvas({\n collections\n}: {\n collections: CollectionConfig[];\n}) {\n const reactFlowInstance = useReactFlow();\n const {\n nodes: layoutedNodes,\n edges: layoutedEdges,\n direction,\n setDirection,\n relayout,\n tableCount,\n relationCount\n } = useSchemaGraph(collections);\n\n const [nodes, setNodes] = useState<Node[]>([]);\n const [edges, setEdges] = useState<Edge[]>([]);\n const [selectedTable, setSelectedTable] = useState<string | null>(null);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const initialFitDone = useRef(false);\n\n // Sync layouted data into state\n useEffect(() => {\n setNodes(layoutedNodes);\n setEdges(layoutedEdges);\n }, [layoutedNodes, layoutedEdges]);\n\n const onNodesChange = useCallback(\n (changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)),\n []\n );\n const onEdgesChange = useCallback(\n (changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)),\n []\n );\n\n // Fit view on first load\n useEffect(() => {\n if (nodes.length > 0 && !initialFitDone.current) {\n const timer = setTimeout(() => {\n reactFlowInstance.fitView({ padding: 0.15,\nduration: 400 });\n initialFitDone.current = true;\n }, 200);\n return () => clearTimeout(timer);\n }\n return undefined;\n }, [nodes.length, reactFlowInstance]);\n\n const handleFitView = useCallback(() => {\n reactFlowInstance.fitView({ padding: 0.15,\nduration: 400 });\n }, [reactFlowInstance]);\n\n const handleNodeClick = useCallback((_: React.MouseEvent, node: Node) => {\n setSelectedTable(node.id);\n }, []);\n\n const handlePaneClick = useCallback(() => {\n setSelectedTable(null);\n }, []);\n\n // Focus on a table from the sidebar\n const handleTableSelect = useCallback(\n (nodeId: string) => {\n setSelectedTable(nodeId);\n const node = nodes.find((n) => n.id === nodeId);\n if (node) {\n reactFlowInstance.setCenter(\n node.position.x + 140,\n node.position.y + 60,\n { zoom: 1.2,\nduration: 400 }\n );\n }\n },\n [nodes, reactFlowInstance]\n );\n\n // Sidebar panel size\n const [sidebarSize, setSidebarSize] = useState(() => {\n try {\n const saved = localStorage.getItem(\"rebase_schema_viz_sidebar_size\");\n return saved !== null ? parseFloat(saved) : 20;\n } catch {\n return 20;\n }\n });\n\n useEffect(() => {\n try {\n localStorage.setItem(\n \"rebase_schema_viz_sidebar_size\",\n sidebarSize.toString()\n );\n } catch {\n // Ignore\n }\n }, [sidebarSize]);\n\n // Group collections for sidebar\n const postgresCollections = useMemo(\n () => collections.filter(isPostgresCollectionConfig),\n [collections]\n );\n\n const filteredCollections = useMemo(() => {\n if (!searchQuery.trim()) return postgresCollections;\n const q = searchQuery.toLowerCase();\n return postgresCollections.filter(\n (c) =>\n c.name.toLowerCase().includes(q) ||\n c.table?.toLowerCase().includes(q) ||\n c.slug?.toLowerCase().includes(q)\n );\n }, [postgresCollections, searchQuery]);\n\n // Junction table nodes\n const junctionNodes = useMemo(\n () =>\n nodes.filter(\n (n) => (n.data as TableNodeData).isJunction\n ),\n [nodes]\n );\n\n // Stats\n const stats = useMemo(\n () => ({\n tables: tableCount,\n relations: relationCount,\n junctions: junctionNodes.length,\n withRls: postgresCollections.filter(\n (c) =>\n isPostgresCollectionConfig(c) &&\n c.securityRules &&\n c.securityRules.length > 0\n ).length\n }),\n [\n tableCount,\n relationCount,\n junctionNodes.length,\n postgresCollections\n ]\n );\n\n return (\n <ResizablePanels\n orientation=\"horizontal\"\n panelSizePercent={sidebarSize}\n onPanelSizeChange={setSidebarSize}\n minPanelSizePx={220}\n firstPanel={\n <div\n className={cls(\n \"flex flex-col h-full w-full bg-white dark:bg-surface-950 border-r\",\n defaultBorderMixin\n )}\n >\n {/* Sidebar header */}\n <div\n className={cls(\n \"flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]\",\n defaultBorderMixin\n )}\n >\n <Typography\n variant=\"caption\"\n className=\"font-bold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark\"\n >\n Tables\n </Typography>\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark font-mono\"\n >\n {stats.tables}\n </Typography>\n </div>\n\n {/* Search */}\n <div className=\"px-2 py-1.5 border-b border-surface-200/40 dark:border-surface-700/40\">\n <SearchBar\n size=\"smallest\"\n placeholder=\"Filter tables…\"\n onTextSearch={(val) => setSearchQuery(val ?? \"\")}\n innerClassName=\"text-xs\"\n />\n </div>\n\n {/* Table list */}\n <div className=\"flex-grow overflow-y-auto no-scrollbar p-1\">\n {/* Regular collections */}\n <div className=\"space-y-0.5\">\n {filteredCollections.map((collection) => {\n const table = collection.table ?? collection.slug;\n const nodeId = `table-${table}`;\n const isSelected = selectedTable === nodeId;\n return (\n <div\n key={nodeId}\n onClick={() =>\n handleTableSelect(nodeId)\n }\n className={cls(\n \"flex items-center p-1.5 cursor-pointer rounded transition-colors group\",\n isSelected\n ? \"bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light\"\n : \"hover:bg-surface-100 dark:hover:bg-surface-950 text-text-secondary dark:text-text-secondary-dark\"\n )}\n >\n <div className=\"shrink-0 mr-1.5 text-text-disabled dark:text-text-disabled-dark\">\n <IconForView\n collectionOrView={{\n slug: collection.slug,\n name: collection.name,\n icon:\n typeof collection.icon ===\n \"string\"\n ? collection.icon\n : undefined\n }}\n size=\"smallest\"\n />\n </div>\n <div className=\"flex flex-col min-w-0 flex-1\">\n <Typography\n variant=\"caption\"\n className=\"text-xs truncate font-medium\"\n >\n {collection.name}\n </Typography>\n {table !== collection.name && (\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark font-mono truncate\"\n >\n {table}\n </Typography>\n )}\n </div>\n <div className=\"flex items-center gap-1 shrink-0 ml-1\">\n {isPostgresCollectionConfig(collection) &&\n collection.securityRules &&\n collection.securityRules\n .length > 0 && (\n <Tooltip title=\"RLS enabled\">\n <div className=\"w-1.5 h-1.5 rounded-full bg-green-500\"/>\n </Tooltip>\n )}\n {collection.history && (\n <Tooltip title=\"History enabled\">\n <div className=\"w-1.5 h-1.5 rounded-full bg-blue-400\"/>\n </Tooltip>\n )}\n </div>\n </div>\n );\n })}\n </div>\n\n {/* Junction tables */}\n {junctionNodes.length > 0 && (\n <div className=\"mt-3\">\n <Typography\n variant=\"caption\"\n className=\"px-1.5 text-[10px] uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark font-medium\"\n >\n Junction Tables\n </Typography>\n <div className=\"mt-1 space-y-0.5\">\n {junctionNodes.map((node) => {\n const d =\n node.data as TableNodeData;\n const isSelected =\n selectedTable === node.id;\n return (\n <div\n key={node.id}\n onClick={() =>\n handleTableSelect(\n node.id\n )\n }\n className={cls(\n \"flex items-center p-1.5 cursor-pointer rounded transition-colors\",\n isSelected\n ? \"bg-primary/10 text-primary dark:bg-primary/20\"\n : \"hover:bg-surface-100 dark:hover:bg-surface-950 text-text-disabled dark:text-text-disabled-dark\"\n )}\n >\n <svg\n className=\"w-3.5 h-3.5 mr-1.5 shrink-0\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4\"\n />\n </svg>\n <Typography\n variant=\"caption\"\n className=\"text-xs font-mono truncate\"\n >\n {d.tableName}\n </Typography>\n </div>\n );\n })}\n </div>\n </div>\n )}\n </div>\n\n {/* Stats footer */}\n <div\n className={cls(\n \"px-3 py-2 border-t bg-surface-50 dark:bg-surface-900 space-y-1\",\n defaultBorderMixin\n )}\n >\n <div className=\"flex items-center justify-between\">\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark\"\n >\n Tables\n </Typography>\n <Typography\n variant=\"caption\"\n className=\"text-[10px] font-mono font-medium\"\n >\n {stats.tables}\n </Typography>\n </div>\n <div className=\"flex items-center justify-between\">\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark\"\n >\n Relations\n </Typography>\n <Typography\n variant=\"caption\"\n className=\"text-[10px] font-mono font-medium\"\n >\n {stats.relations}\n </Typography>\n </div>\n <div className=\"flex items-center justify-between\">\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark\"\n >\n RLS protected\n </Typography>\n <div className=\"flex items-center gap-1\">\n <div className=\"w-1.5 h-1.5 rounded-full bg-green-500\"/>\n <Typography\n variant=\"caption\"\n className=\"text-[10px] font-mono font-medium\"\n >\n {stats.withRls}\n </Typography>\n </div>\n </div>\n </div>\n </div>\n }\n secondPanel={\n <div className=\"flex-grow flex flex-col min-w-0 h-full w-full bg-white dark:bg-surface-950\">\n {/* Toolbar */}\n <div\n className={cls(\n \"flex items-center justify-between pr-2 border-b bg-white dark:bg-surface-950 min-h-[46px]\",\n defaultBorderMixin\n )}\n >\n <div className=\"flex items-center gap-2 px-4\">\n <Typography\n variant=\"subtitle2\"\n className=\"font-mono text-text-secondary dark:text-text-secondary-dark\"\n >\n Schema Visualizer\n </Typography>\n </div>\n <div className=\"flex shrink-0 items-center gap-1.5\">\n {/* Direction toggle */}\n <div className=\"flex items-center bg-surface-100 dark:bg-surface-950 rounded-md border border-surface-200/40 dark:border-surface-700/40\">\n <Tooltip title=\"Left to right layout\">\n <Button\n size=\"small\"\n variant={direction === \"LR\" ? \"filled\" : \"text\"}\n color={direction === \"LR\" ? \"primary\" : \"neutral\"}\n onClick={() => setDirection(\"LR\")}\n className=\"!rounded-r-none !px-2 !py-1 !text-[10px] !font-mono\"\n >\n LR\n </Button>\n </Tooltip>\n <Tooltip title=\"Top to bottom layout\">\n <Button\n size=\"small\"\n variant={direction === \"TB\" ? \"filled\" : \"text\"}\n color={direction === \"TB\" ? \"primary\" : \"neutral\"}\n onClick={() => setDirection(\"TB\")}\n className=\"!rounded-l-none !px-2 !py-1 !text-[10px] !font-mono\"\n >\n TB\n </Button>\n </Tooltip>\n </div>\n\n <div className=\"h-4 w-px bg-surface-200 dark:bg-surface-950 mx-0.5\"/>\n\n {/* Fit view */}\n <Tooltip title=\"Fit to view\">\n <IconButton\n size=\"small\"\n onClick={handleFitView}\n >\n <svg\n className=\"w-4 h-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4\"\n />\n </svg>\n </IconButton>\n </Tooltip>\n\n {/* Re-layout */}\n <Tooltip title=\"Re-layout\">\n <IconButton\n size=\"small\"\n onClick={relayout}\n >\n <svg\n className=\"w-4 h-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\"\n />\n </svg>\n </IconButton>\n </Tooltip>\n </div>\n </div>\n\n {/* Canvas */}\n <div className=\"flex-grow relative\">\n <ReactFlow\n nodes={nodes}\n edges={edges}\n onNodesChange={onNodesChange}\n onEdgesChange={onEdgesChange}\n onNodeClick={handleNodeClick}\n onPaneClick={handlePaneClick}\n nodeTypes={nodeTypes}\n edgeTypes={edgeTypes}\n fitView\n fitViewOptions={{ padding: 0.15 }}\n minZoom={0.1}\n maxZoom={2}\n proOptions={{ hideAttribution: true }}\n className=\"bg-surface-50 dark:bg-surface-950\"\n >\n <Background\n variant={BackgroundVariant.Dots}\n gap={20}\n size={1}\n className=\"!bg-surface-50 dark:!bg-surface-950\"\n color=\"var(--rf-bg-dot, #d4d4d8)\"\n />\n <Controls\n showInteractive={false}\n className=\"!bg-white dark:!bg-surface-900 !border !border-surface-200/40 dark:!border-surface-700/40 !shadow-sm !rounded-lg\"\n />\n <MiniMap\n nodeStrokeColor={(n) => {\n const d = n.data as TableNodeData;\n if (d.isJunction) return \"#a78bfa\";\n if (d.rlsEnabled) return \"#22c55e\";\n return \"#6366f1\";\n }}\n nodeColor={(n) => {\n const d = n.data as TableNodeData;\n if (d.isJunction) return \"#ede9fe\";\n return \"#eef2ff\";\n }}\n maskColor=\"rgba(0,0,0,0.08)\"\n className=\"!bg-white dark:!bg-surface-900 !border !border-surface-200/40 dark:!border-surface-700/40 !shadow-sm !rounded-lg\"\n />\n </ReactFlow>\n\n {/* Legend overlay */}\n <div className=\"absolute bottom-4 left-4 flex items-center gap-3 px-3 py-2 bg-white/90 dark:bg-surface-900/90 backdrop-blur-sm rounded-lg border border-surface-200/40 dark:border-surface-700/40 shadow-sm\">\n <div className=\"flex items-center gap-1.5\">\n <div className=\"w-6 h-0.5 bg-indigo-500 rounded\"/>\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark\"\n >\n Owning\n </Typography>\n </div>\n <div className=\"flex items-center gap-1.5\">\n <div className=\"w-6 h-0.5 bg-violet-500 rounded\"/>\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark\"\n >\n M:N\n </Typography>\n </div>\n <div className=\"flex items-center gap-1.5\">\n <div\n className=\"w-6 h-0.5 rounded\"\n style={{\n backgroundImage:\n \"repeating-linear-gradient(90deg, #94a3b8, #94a3b8 4px, transparent 4px, transparent 7px)\"\n }}\n />\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark\"\n >\n Inverse\n </Typography>\n </div>\n <div className=\"h-3 w-px bg-surface-200 dark:bg-surface-700\"/>\n <div className=\"flex items-center gap-1\">\n <span className=\"text-[9px]\">🔑</span>\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark\"\n >\n PK\n </Typography>\n </div>\n <div className=\"flex items-center gap-1\">\n <span className=\"text-[9px]\">🔗</span>\n <Typography\n variant=\"caption\"\n className=\"text-[10px] text-text-disabled dark:text-text-disabled-dark\"\n >\n FK\n </Typography>\n </div>\n </div>\n </div>\n </div>\n }\n />\n );\n}\n\n// ─── Outer wrapper (provides ReactFlowProvider) ────────────────────────\n\nexport const SchemaVisualizer = () => {\n const { collections: registryCollections } =\n useStudioCollectionRegistry() as {\n collections?: CollectionConfig[];\n };\n\n // Merge registry collections with any passed collections\n const collections = useMemo(() => {\n return registryCollections ?? [];\n }, [registryCollections]);\n\n if (!collections || collections.length === 0) {\n return (\n <div className=\"flex items-center justify-center h-full w-full\">\n <div className=\"text-center space-y-3\">\n <CircularProgress size=\"small\"/>\n <Typography\n variant=\"body2\"\n color=\"secondary\"\n >\n Loading schema…\n </Typography>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex h-full w-full bg-white dark:bg-surface-950 overflow-hidden text-text-primary dark:text-text-primary-dark\">\n <ReactFlowProvider>\n <SchemaVisualizerCanvas collections={collections}/>\n </ReactFlowProvider>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Node, Edge } from "@xyflow/react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { CollectionConfig } from "@rebasepro/types";
|
|
3
3
|
import type { LayoutDirection } from "./schema-visualizer.utils";
|
|
4
4
|
export interface ColumnInfo {
|
|
5
5
|
name: string;
|
|
@@ -34,4 +34,4 @@ export interface UseSchemaGraphResult {
|
|
|
34
34
|
tableCount: number;
|
|
35
35
|
relationCount: number;
|
|
36
36
|
}
|
|
37
|
-
export declare const useSchemaGraph: (collections:
|
|
37
|
+
export declare const useSchemaGraph: (collections: CollectionConfig[] | undefined) => UseSchemaGraphResult;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { StudioBridgeProvider, StudioBridgeContext, useStudioCollectionRegistry,
|
|
1
|
+
export { StudioBridgeProvider, StudioBridgeContext, useStudioCollectionRegistry, useStudioSidePanelController, useStudioUrlController, useStudioNavigationState, useStudioBreadcrumbs } from "@rebasepro/core";
|
|
2
2
|
export type { StudioBridge, BreadcrumbEntry, BreadcrumbsController } from "@rebasepro/core";
|
|
3
3
|
export * from "./components/RebaseStudio";
|
|
4
4
|
export * from "./components/StudioHomePage";
|
package/dist/index.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IconForView, SchemaDriftBanner, StudioBridgeContext, StudioBridgeProvider, useRebaseContext, useRebaseRegistryDispatch, useRestoreScroll, useSlot, useStudioBreadcrumbs, useStudioBreadcrumbs as useStudioBreadcrumbs$1, useStudioCollectionRegistry, useStudioNavigationState,
|
|
1
|
+
import { IconForView, SchemaDriftBanner, StudioBridgeContext, StudioBridgeProvider, useRebaseContext, useRebaseRegistryDispatch, useRestoreScroll, useSlot, useStudioBreadcrumbs, useStudioBreadcrumbs as useStudioBreadcrumbs$1, useStudioCollectionRegistry, useStudioNavigationState, useStudioSidePanelController, useStudioUrlController } from "@rebasepro/core";
|
|
2
2
|
import React, { Suspense, lazy, useEffect, useLayoutEffect, useMemo, useState } from "react";
|
|
3
3
|
import { Card, CircularProgressCenter, Container, ExpandablePanel, Typography, cls } from "@rebasepro/ui";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -596,12 +596,12 @@ function SyntaxHighlightedSnippet() {
|
|
|
596
596
|
}
|
|
597
597
|
//#endregion
|
|
598
598
|
//#region src/components/RebaseStudio.tsx
|
|
599
|
-
var SQLEditor = lazy(() => import("./SQLEditor-
|
|
600
|
-
var JSEditor = lazy(() => import("./JSEditor-
|
|
601
|
-
var RLSEditor = lazy(() => import("./RLSEditor-
|
|
599
|
+
var SQLEditor = lazy(() => import("./SQLEditor-CgR2hlUI.js").then((m) => ({ default: m.SQLEditor })));
|
|
600
|
+
var JSEditor = lazy(() => import("./JSEditor-Sgmw1SHC.js").then((m) => ({ default: m.JSEditor })));
|
|
601
|
+
var RLSEditor = lazy(() => import("./RLSEditor-DPhky8XW.js").then((m) => ({ default: m.RLSEditor })));
|
|
602
602
|
var StorageView = lazy(() => import("./StorageView-BMhD29YO.js").then((m) => ({ default: m.StorageView })));
|
|
603
603
|
var CronJobsView = lazy(() => import("./CronJobsView-4gdtJvoe.js").then((m) => ({ default: m.CronJobsView })));
|
|
604
|
-
var SchemaVisualizer = lazy(() => import("./SchemaVisualizer-
|
|
604
|
+
var SchemaVisualizer = lazy(() => import("./SchemaVisualizer-WK4ZZR5X.js").then((m) => ({ default: m.SchemaVisualizer })));
|
|
605
605
|
var BranchesView = lazy(() => import("./BranchesView-Dlg78EQ8.js").then((m) => ({ default: m.BranchesView })));
|
|
606
606
|
var ApiExplorer = lazy(() => import("./ApiExplorer-CdIwR9Ga.js").then((m) => ({ default: m.ApiExplorer })));
|
|
607
607
|
var LogsExplorer = lazy(() => import("./LogsExplorer-J4xfsuv3.js").then((m) => ({ default: m.LogsExplorer })));
|
|
@@ -735,6 +735,6 @@ function RebaseStudio({ tools, homePage }) {
|
|
|
735
735
|
return null;
|
|
736
736
|
}
|
|
737
737
|
//#endregion
|
|
738
|
-
export { RebaseStudio, StudioBridgeContext, StudioBridgeProvider, StudioHomePage, useStudioBreadcrumbs, useStudioCollectionRegistry, useStudioNavigationState,
|
|
738
|
+
export { RebaseStudio, StudioBridgeContext, StudioBridgeProvider, StudioHomePage, useStudioBreadcrumbs, useStudioCollectionRegistry, useStudioNavigationState, useStudioSidePanelController, useStudioUrlController };
|
|
739
739
|
|
|
740
740
|
//# sourceMappingURL=index.es.js.map
|
package/dist/index.umd.js
CHANGED
|
@@ -1666,7 +1666,7 @@
|
|
|
1666
1666
|
};
|
|
1667
1667
|
SQLEditor$1 = () => {
|
|
1668
1668
|
const { databaseAdmin, client } = (0, _rebasepro_core.useRebaseContext)();
|
|
1669
|
-
const
|
|
1669
|
+
const sidePanelController = (0, _rebasepro_core.useStudioSidePanelController)();
|
|
1670
1670
|
const snackbarController = (0, _rebasepro_core.useSnackbarController)();
|
|
1671
1671
|
const collectionRegistry = (0, _rebasepro_core.useStudioCollectionRegistry)();
|
|
1672
1672
|
const { t } = (0, _rebasepro_core.useTranslation)();
|
|
@@ -2428,7 +2428,7 @@
|
|
|
2428
2428
|
className: "text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300 transition-colors",
|
|
2429
2429
|
onClick: (e) => {
|
|
2430
2430
|
e.stopPropagation();
|
|
2431
|
-
|
|
2431
|
+
sidePanelController?.open({
|
|
2432
2432
|
path: ra.collection.collection.slug,
|
|
2433
2433
|
entityId: ra.entityId,
|
|
2434
2434
|
collection: ra.collection.collection,
|
|
@@ -2452,7 +2452,7 @@
|
|
|
2452
2452
|
children: rowActions.map((ra) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.MenuItem, {
|
|
2453
2453
|
dense: true,
|
|
2454
2454
|
onClick: () => {
|
|
2455
|
-
|
|
2455
|
+
sidePanelController?.open({
|
|
2456
2456
|
path: ra.collection.collection.slug,
|
|
2457
2457
|
entityId: ra.entityId,
|
|
2458
2458
|
collection: ra.collection.collection,
|
|
@@ -2867,11 +2867,7 @@
|
|
|
2867
2867
|
REBASE_CLIENT_TYPES = `
|
|
2868
2868
|
// ─── Rebase Client SDK Type Definitions ─────────────────────────────
|
|
2869
2869
|
|
|
2870
|
-
|
|
2871
|
-
id: string | number;
|
|
2872
|
-
path: string;
|
|
2873
|
-
values: M;
|
|
2874
|
-
}
|
|
2870
|
+
/** A flat database row with the id merged at the top level. */
|
|
2875
2871
|
|
|
2876
2872
|
interface FindParams {
|
|
2877
2873
|
limit?: number;
|
|
@@ -2883,8 +2879,8 @@ interface FindParams {
|
|
|
2883
2879
|
searchString?: string;
|
|
2884
2880
|
}
|
|
2885
2881
|
|
|
2886
|
-
interface
|
|
2887
|
-
data:
|
|
2882
|
+
interface FindResult<M extends Record<string, any> = any> {
|
|
2883
|
+
data: M[];
|
|
2888
2884
|
meta: {
|
|
2889
2885
|
total: number;
|
|
2890
2886
|
limit: number;
|
|
@@ -2901,55 +2897,55 @@ interface QueryBuilder<M extends Record<string, any> = any> {
|
|
|
2901
2897
|
limit(count: number): QueryBuilder<M>;
|
|
2902
2898
|
offset(count: number): QueryBuilder<M>;
|
|
2903
2899
|
search(searchString: string): QueryBuilder<M>;
|
|
2904
|
-
find(): Promise<
|
|
2905
|
-
findOne(): Promise<Entity<M> | undefined>;
|
|
2900
|
+
find(): Promise<FindResult<M>>;
|
|
2906
2901
|
count(): Promise<number>;
|
|
2907
2902
|
}
|
|
2908
2903
|
|
|
2909
2904
|
interface CollectionClient<M extends Record<string, any> = any> {
|
|
2910
|
-
find(params?: FindParams): Promise<
|
|
2911
|
-
findById(id: string | number): Promise<
|
|
2912
|
-
create(data: Partial<M>, id?: string | number): Promise<
|
|
2913
|
-
update(id: string | number, data: Partial<M>): Promise<
|
|
2905
|
+
find(params?: FindParams): Promise<FindResult<M>>;
|
|
2906
|
+
findById(id: string | number): Promise<M | undefined>;
|
|
2907
|
+
create(data: Partial<M>, id?: string | number): Promise<M>;
|
|
2908
|
+
update(id: string | number, data: Partial<M>): Promise<M>;
|
|
2914
2909
|
delete(id: string | number): Promise<void>;
|
|
2915
2910
|
where(column: keyof M & string, operator: WhereFilterOp, value: any): QueryBuilder<M>;
|
|
2916
2911
|
orderBy(column: keyof M & string, direction?: "asc" | "desc"): QueryBuilder<M>;
|
|
2917
2912
|
limit(count: number): QueryBuilder<M>;
|
|
2918
2913
|
offset(count: number): QueryBuilder<M>;
|
|
2919
2914
|
search(searchString: string): QueryBuilder<M>;
|
|
2920
|
-
listen?(params: FindParams | undefined, onUpdate: (response:
|
|
2921
|
-
listenById?(id: string | number, onUpdate: (
|
|
2915
|
+
listen?(params: FindParams | undefined, onUpdate: (response: FindResult<M>) => void, onError?: (error: Error) => void): () => void;
|
|
2916
|
+
listenById?(id: string | number, onUpdate: (row: M | undefined) => void, onError?: (error: Error) => void): () => void;
|
|
2922
2917
|
count?(params?: FindParams): Promise<number>;
|
|
2923
2918
|
}
|
|
2924
2919
|
|
|
2925
|
-
interface
|
|
2920
|
+
interface User {
|
|
2926
2921
|
uid: string;
|
|
2927
2922
|
email: string | null;
|
|
2928
2923
|
displayName: string | null;
|
|
2929
2924
|
photoURL: string | null;
|
|
2930
|
-
emailVerified?: boolean;
|
|
2931
|
-
roles?: string[];
|
|
2932
2925
|
providerId: string;
|
|
2933
2926
|
isAnonymous: boolean;
|
|
2927
|
+
emailVerified?: boolean;
|
|
2928
|
+
roles?: string[];
|
|
2929
|
+
metadata?: Record<string, unknown>;
|
|
2934
2930
|
}
|
|
2935
2931
|
|
|
2936
2932
|
interface RebaseSession {
|
|
2937
2933
|
accessToken: string;
|
|
2938
2934
|
refreshToken: string;
|
|
2939
2935
|
expiresAt: number;
|
|
2940
|
-
user:
|
|
2936
|
+
user: User;
|
|
2941
2937
|
}
|
|
2942
2938
|
|
|
2943
2939
|
type AuthChangeEvent = 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'USER_UPDATED';
|
|
2944
2940
|
|
|
2945
2941
|
interface RebaseAuth {
|
|
2946
|
-
signInWithEmail(email: string, password: string): Promise<{ user:
|
|
2947
|
-
signUp(email: string, password: string, displayName?: string): Promise<{ user:
|
|
2948
|
-
signInWithGoogle(idToken: string): Promise<{ user:
|
|
2942
|
+
signInWithEmail(email: string, password: string): Promise<{ user: User; accessToken: string; refreshToken: string }>;
|
|
2943
|
+
signUp(email: string, password: string, displayName?: string): Promise<{ user: User; accessToken: string; refreshToken: string }>;
|
|
2944
|
+
signInWithGoogle(idToken: string): Promise<{ user: User; accessToken: string; refreshToken: string }>;
|
|
2949
2945
|
signOut(): Promise<void>;
|
|
2950
2946
|
refreshSession(): Promise<RebaseSession>;
|
|
2951
|
-
getUser(): Promise<
|
|
2952
|
-
updateUser(updates: { displayName?: string; photoURL?: string }): Promise<
|
|
2947
|
+
getUser(): Promise<User>;
|
|
2948
|
+
updateUser(updates: { displayName?: string; photoURL?: string }): Promise<User>;
|
|
2953
2949
|
resetPasswordForEmail(email: string): Promise<{ success: boolean; message: string }>;
|
|
2954
2950
|
resetPassword(token: string, password: string): Promise<{ success: boolean; message: string }>;
|
|
2955
2951
|
changePassword(oldPassword: string, newPassword: string): Promise<{ success: boolean; message: string }>;
|
|
@@ -3639,7 +3635,7 @@ declare const context: JSEditorContext;
|
|
|
3639
3635
|
const apiConfig = (0, _rebasepro_core.useApiConfig)();
|
|
3640
3636
|
const snackbar = (0, _rebasepro_core.useSnackbarController)();
|
|
3641
3637
|
const collectionRegistry = (0, _rebasepro_core.useStudioCollectionRegistry)();
|
|
3642
|
-
const
|
|
3638
|
+
const sidePanelController = (0, _rebasepro_core.useStudioSidePanelController)();
|
|
3643
3639
|
const { t } = (0, _rebasepro_core.useTranslation)();
|
|
3644
3640
|
const currentUser = rebaseContext.authController?.user;
|
|
3645
3641
|
const [tabs, setTabs] = (0, react.useState)(() => loadFromStorage("tabs", [{
|
|
@@ -4274,7 +4270,7 @@ declare const context: JSEditorContext;
|
|
|
4274
4270
|
className: "text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300",
|
|
4275
4271
|
onClick: (e) => {
|
|
4276
4272
|
e.stopPropagation();
|
|
4277
|
-
|
|
4273
|
+
sidePanelController.open({
|
|
4278
4274
|
path: ra.collection.collectionSlug,
|
|
4279
4275
|
entityId: ra.entityId,
|
|
4280
4276
|
collection: ra.collection.collection,
|
|
@@ -4298,7 +4294,7 @@ declare const context: JSEditorContext;
|
|
|
4298
4294
|
children: rowActions.map((ra) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.MenuItem, {
|
|
4299
4295
|
dense: true,
|
|
4300
4296
|
onClick: () => {
|
|
4301
|
-
|
|
4297
|
+
sidePanelController.open({
|
|
4302
4298
|
path: ra.collection.collectionSlug,
|
|
4303
4299
|
entityId: ra.entityId,
|
|
4304
4300
|
collection: ra.collection.collection,
|
|
@@ -5324,28 +5320,18 @@ return result;
|
|
|
5324
5320
|
status: "live"
|
|
5325
5321
|
};
|
|
5326
5322
|
});
|
|
5327
|
-
if (activeCollection && (0, _rebasepro_types.
|
|
5323
|
+
if (activeCollection && (0, _rebasepro_types.isPostgresCollectionConfig)(activeCollection) && activeCollection.securityRules) activeCollection.securityRules.forEach((rule) => {
|
|
5328
5324
|
const ruleName = rule.name;
|
|
5329
5325
|
if (!ruleName) return;
|
|
5330
|
-
|
|
5331
|
-
policyname: ruleName,
|
|
5332
|
-
tablename: activeTableData.tableName,
|
|
5333
|
-
permissive: (rule.mode || "permissive").toUpperCase(),
|
|
5334
|
-
cmd: (rule.operation || "ALL").toUpperCase(),
|
|
5335
|
-
roles: rule.roles || ["public"],
|
|
5336
|
-
qual: rule.using || null,
|
|
5337
|
-
with_check: rule.withCheck || null,
|
|
5338
|
-
status: "both"
|
|
5339
|
-
};
|
|
5340
|
-
else policiesMap[ruleName] = {
|
|
5326
|
+
policiesMap[ruleName] = {
|
|
5341
5327
|
policyname: ruleName,
|
|
5342
5328
|
tablename: activeTableData.tableName,
|
|
5343
5329
|
permissive: (rule.mode || "permissive").toUpperCase(),
|
|
5344
5330
|
cmd: (rule.operation || "ALL").toUpperCase(),
|
|
5345
|
-
roles: rule.roles
|
|
5331
|
+
roles: [...rule.roles ?? ["public"]],
|
|
5346
5332
|
qual: rule.using || null,
|
|
5347
5333
|
with_check: rule.withCheck || null,
|
|
5348
|
-
status: "code_only"
|
|
5334
|
+
status: policiesMap[ruleName] ? "both" : "code_only"
|
|
5349
5335
|
};
|
|
5350
5336
|
});
|
|
5351
5337
|
return Object.values(policiesMap).sort((a, b) => a.policyname.localeCompare(b.policyname));
|
|
@@ -5726,7 +5712,7 @@ return result;
|
|
|
5726
5712
|
withCheck: newPolicy.with_check || void 0,
|
|
5727
5713
|
roles: newPolicy.roles
|
|
5728
5714
|
};
|
|
5729
|
-
const existingRules = ((0, _rebasepro_types.
|
|
5715
|
+
const existingRules = ((0, _rebasepro_types.isPostgresCollectionConfig)(activeCollection) ? activeCollection.securityRules : void 0) || [];
|
|
5730
5716
|
let newRules;
|
|
5731
5717
|
if (editingPolicy === "new") newRules = [...existingRules, rule];
|
|
5732
5718
|
else newRules = existingRules.map((r) => r.name === editingPolicy.policyname ? rule : r);
|
|
@@ -5923,7 +5909,7 @@ return result;
|
|
|
5923
5909
|
withCheck: policy.with_check || void 0,
|
|
5924
5910
|
roles: policy.roles
|
|
5925
5911
|
};
|
|
5926
|
-
const newRules = [...((0, _rebasepro_types.
|
|
5912
|
+
const newRules = [...((0, _rebasepro_types.isPostgresCollectionConfig)(activeCollection) ? activeCollection.securityRules : void 0) || [], rule];
|
|
5927
5913
|
try {
|
|
5928
5914
|
if (!(await fetch(`${apiUrl}/api/schema-editor/collection/save`, {
|
|
5929
5915
|
method: "POST",
|
|
@@ -8055,7 +8041,7 @@ return result;
|
|
|
8055
8041
|
enumValues
|
|
8056
8042
|
});
|
|
8057
8043
|
}
|
|
8058
|
-
if ((0, _rebasepro_types.
|
|
8044
|
+
if ((0, _rebasepro_types.isPostgresCollectionConfig)(collection)) try {
|
|
8059
8045
|
const resolvedRelations = (0, _rebasepro_common.resolveCollectionRelations)(collection);
|
|
8060
8046
|
for (const rel of Object.values(resolvedRelations)) if (rel.direction === "owning" && rel.cardinality === "one" && rel.localKey) if (!columns.some((c) => c.name === rel.localKey)) columns.push({
|
|
8061
8047
|
name: rel.localKey,
|
|
@@ -8092,7 +8078,7 @@ return result;
|
|
|
8092
8078
|
return fk ? `source-${fk.name}` : "source-default";
|
|
8093
8079
|
};
|
|
8094
8080
|
for (const collection of collections) {
|
|
8095
|
-
if (!(0, _rebasepro_types.
|
|
8081
|
+
if (!(0, _rebasepro_types.isPostgresCollectionConfig)(collection)) continue;
|
|
8096
8082
|
const tableName = collection.table ?? collection.slug;
|
|
8097
8083
|
const nodeId = `table-${tableName}`;
|
|
8098
8084
|
tableToNodeId.set(tableName, nodeId);
|
|
@@ -8121,7 +8107,7 @@ return result;
|
|
|
8121
8107
|
}
|
|
8122
8108
|
const processedJunctions = /* @__PURE__ */ new Set();
|
|
8123
8109
|
for (const collection of collections) {
|
|
8124
|
-
if (!(0, _rebasepro_types.
|
|
8110
|
+
if (!(0, _rebasepro_types.isPostgresCollectionConfig)(collection)) continue;
|
|
8125
8111
|
const tableName = collection.table ?? collection.slug;
|
|
8126
8112
|
const sourceNodeId = tableToNodeId.get(tableName);
|
|
8127
8113
|
if (!sourceNodeId) continue;
|
|
@@ -8138,7 +8124,7 @@ return result;
|
|
|
8138
8124
|
} catch {
|
|
8139
8125
|
continue;
|
|
8140
8126
|
}
|
|
8141
|
-
if (!(0, _rebasepro_types.
|
|
8127
|
+
if (!(0, _rebasepro_types.isPostgresCollectionConfig)(targetCollection)) continue;
|
|
8142
8128
|
const targetTable = targetCollection.table ?? targetCollection.slug;
|
|
8143
8129
|
const targetNodeId = tableToNodeId.get(targetTable);
|
|
8144
8130
|
if (!targetNodeId) continue;
|
|
@@ -8611,7 +8597,7 @@ return result;
|
|
|
8611
8597
|
localStorage.setItem("rebase_schema_viz_sidebar_size", sidebarSize.toString());
|
|
8612
8598
|
} catch {}
|
|
8613
8599
|
}, [sidebarSize]);
|
|
8614
|
-
const postgresCollections = (0, react.useMemo)(() => collections.filter(_rebasepro_types.
|
|
8600
|
+
const postgresCollections = (0, react.useMemo)(() => collections.filter(_rebasepro_types.isPostgresCollectionConfig), [collections]);
|
|
8615
8601
|
const filteredCollections = (0, react.useMemo)(() => {
|
|
8616
8602
|
if (!searchQuery.trim()) return postgresCollections;
|
|
8617
8603
|
const q = searchQuery.toLowerCase();
|
|
@@ -8622,7 +8608,7 @@ return result;
|
|
|
8622
8608
|
tables: tableCount,
|
|
8623
8609
|
relations: relationCount,
|
|
8624
8610
|
junctions: junctionNodes.length,
|
|
8625
|
-
withRls: postgresCollections.filter((c) => (0, _rebasepro_types.
|
|
8611
|
+
withRls: postgresCollections.filter((c) => (0, _rebasepro_types.isPostgresCollectionConfig)(c) && c.securityRules && c.securityRules.length > 0).length
|
|
8626
8612
|
}), [
|
|
8627
8613
|
tableCount,
|
|
8628
8614
|
relationCount,
|
|
@@ -8694,7 +8680,7 @@ return result;
|
|
|
8694
8680
|
}),
|
|
8695
8681
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8696
8682
|
className: "flex items-center gap-1 shrink-0 ml-1",
|
|
8697
|
-
children: [(0, _rebasepro_types.
|
|
8683
|
+
children: [(0, _rebasepro_types.isPostgresCollectionConfig)(collection) && collection.securityRules && collection.securityRules.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
|
|
8698
8684
|
title: "RLS enabled",
|
|
8699
8685
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-green-500" })
|
|
8700
8686
|
}), collection.history && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
|
|
@@ -11474,10 +11460,10 @@ return result;
|
|
|
11474
11460
|
return _rebasepro_core.useStudioNavigationState;
|
|
11475
11461
|
}
|
|
11476
11462
|
});
|
|
11477
|
-
Object.defineProperty(exports, "
|
|
11463
|
+
Object.defineProperty(exports, "useStudioSidePanelController", {
|
|
11478
11464
|
enumerable: true,
|
|
11479
11465
|
get: function() {
|
|
11480
|
-
return _rebasepro_core.
|
|
11466
|
+
return _rebasepro_core.useStudioSidePanelController;
|
|
11481
11467
|
}
|
|
11482
11468
|
});
|
|
11483
11469
|
Object.defineProperty(exports, "useStudioUrlController", {
|