refactoring

This commit is contained in:
2026-03-12 17:14:33 +01:00
parent e29c5d643c
commit f5b12949d3
6 changed files with 430 additions and 302 deletions

View File

@@ -0,0 +1,142 @@
/**
* Pure functions to build "connected node ids" and source signatures for the
* rendering pipeline. Used by useRenderingNodeState so the hook stays thin and
* this logic is testable in isolation.
*/
import { getConfigContent } from '@/lib/graph/rendering'
import { isReachable, resolveExtendsRef, getTemplateRefs } from '@/lib/graph/templateRefs'
export type NodeLike = { id: string; type?: string; data?: unknown }
export type EdgeLike = { source: string; target: string }
/**
* Collects all node ids that affect the render: direct incoming nodes plus
* config nodes reached via template refs (extends/include/import) that are
* reachable to the render node.
*/
export function buildConnectedNodeIds(
nodes: NodeLike[],
edges: EdgeLike[],
renderNodeId: string,
incomingIds: string[],
getContent: (data: Record<string, unknown> | undefined) => string = getConfigContent
): Set<string> {
const out = new Set<string>()
const addConfigRefs = (nodeId: string, visited: Set<string>) => {
if (visited.has(nodeId)) return
const node = nodes.find((n) => n.id === nodeId && n.type === 'config')
if (!node) return
visited.add(nodeId)
out.add(nodeId)
const content = getContent((node.data ?? undefined) as Record<string, unknown> | undefined)
for (const ref of getTemplateRefs(content)) {
const refId = resolveExtendsRef(nodes, ref)
if (
refId &&
nodes.some((n) => n.id === refId && n.type === 'config') &&
isReachable(edges, refId, renderNodeId)
) {
addConfigRefs(refId, visited)
}
}
}
const configVisited = new Set<string>()
for (const nid of incomingIds) {
const node = nodes.find((n) => n.id === nid)
if (node?.type === 'config') addConfigRefs(nid, configVisited)
else out.add(nid)
}
for (const e of edges) {
if (out.has(e.target)) out.add(e.source)
}
return out
}
export type SourceSignatures = {
connectedNodeIds: Set<string>
configSignature: string
edgesSignature: string
variablesSignature: string
functionsSignature: string
dataSignature: string
sourceSignature: string
}
/**
* Builds connected node set and all signatures used to decide when to re-run
* the resolve → render pipeline.
*/
export function buildSourceSignatures(
nodes: NodeLike[],
edges: EdgeLike[],
renderNodeId: string,
incomingIds: string[],
getContent: (data: Record<string, unknown> | undefined) => string = getConfigContent
): SourceSignatures {
const connectedNodeIds = buildConnectedNodeIds(
nodes,
edges,
renderNodeId,
incomingIds,
getContent
)
const configSignature = nodes
.filter((n) => n.type === 'config' && connectedNodeIds.has(n.id))
.map(
(n) =>
`${n.id}:${(n.data as { title?: string })?.title ?? ''}:${getContent(n.data as Record<string, unknown>)}`
)
.sort()
.join('|')
const edgesSignature = edges
.filter(
(e) =>
connectedNodeIds.has(e.source) &&
(connectedNodeIds.has(e.target) || e.target === renderNodeId)
)
.map((e) => `${e.source}->${e.target}`)
.sort()
.join('|')
const variablesSignature = nodes
.filter((n) => n.type === 'variable' && connectedNodeIds.has(n.id))
.map((n) => `${n.id}:${(n.data as { value?: unknown })?.value}`)
.sort()
.join('|')
const functionsSignature = nodes
.filter((n) => n.type === 'function' && connectedNodeIds.has(n.id))
.map((n) => `${n.id}:${(n.data as { body?: string })?.body ?? ''}`)
.sort()
.join('|')
const dataSignature = nodes
.filter((n) => n.type === 'data' && connectedNodeIds.has(n.id))
.map(
(n) =>
`${n.id}:${JSON.stringify((n.data as { rows?: unknown[] })?.rows ?? [])}:${JSON.stringify((n.data as { hiddenColumns?: unknown[] })?.hiddenColumns ?? [])}`
)
.sort()
.join('|')
const sourceSignature = JSON.stringify({
configSignature,
edgesSignature,
variablesSignature,
functionsSignature,
dataSignature,
})
return {
connectedNodeIds,
configSignature,
edgesSignature,
variablesSignature,
functionsSignature,
dataSignature,
sourceSignature,
}
}