@fortemi/graph 2026.6.5 → 2026.6.7
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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -299,6 +299,6 @@ declare class GraphController {
|
|
|
299
299
|
saveCurrentCommunity(input: CommunityCreateInput): Promise<CommunitySourceDescriptor>;
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
declare const VERSION = "2026.6.
|
|
302
|
+
declare const VERSION = "2026.6.7";
|
|
303
303
|
|
|
304
304
|
export { COMMUNITY_COLORS, type CommunityGraph, type ExpandOptions, type FitOptions, GRAPH_SNAPSHOT_VERSION, type GraphBounds, type GraphCommunity, GraphController, type GraphControllerDb, type GraphControllerListener, type GraphControllerOptions, type GraphControllerState, type GraphControllerStatus, type GraphEdge, type GraphFilter, type GraphLayoutAlgorithm, type GraphLayoutState, type GraphNode, type GraphSnapshot, type GraphSourceMode, type GraphSourceRef, type GraphTransitionState, type LayoutOptions, type NodeRadiusOptions, type PositionedGraph, type PositionedGraphNode, type SerializeSnapshotOptions, UNASSIGNED_COMMUNITY_COLOR, VERSION, type ViewportTransform, buildAdjacency, colorForCommunity, computeDegrees, computeGraphBounds, deserializeGraphSnapshot, expandNeighborhood, filterCommunityGraph, fitGraphToViewport, layoutCommunityGraph, neighborhoodSubgraph, neighborsOf, nodeRadius, serializeGraphSnapshot, stringifyGraphSnapshot, subgraphForNodes };
|
package/dist/index.js
CHANGED
|
@@ -432,7 +432,7 @@ var GraphController = class _GraphController {
|
|
|
432
432
|
};
|
|
433
433
|
|
|
434
434
|
// src/index.ts
|
|
435
|
-
var VERSION = "2026.6.
|
|
435
|
+
var VERSION = "2026.6.7";
|
|
436
436
|
|
|
437
437
|
export { COMMUNITY_COLORS, GRAPH_SNAPSHOT_VERSION, GraphController, UNASSIGNED_COMMUNITY_COLOR, VERSION, buildAdjacency, colorForCommunity, computeDegrees, computeGraphBounds, deserializeGraphSnapshot, expandNeighborhood, filterCommunityGraph, fitGraphToViewport, layoutCommunityGraph, neighborhoodSubgraph, neighborsOf, nodeRadius, serializeGraphSnapshot, stringifyGraphSnapshot, subgraphForNodes };
|
|
438
438
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/degree.ts","../src/color.ts","../src/filter.ts","../src/layout.ts","../src/bounds.ts","../src/neighborhood.ts","../src/serialize.ts","../src/controller.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { CommunityGraph } from './types.js'\n\n/** Count the (undirected) degree of every node, including isolated nodes (0). */\nexport function computeDegrees(graph: CommunityGraph): Map<string, number> {\n const degree = new Map<string, number>()\n for (const node of graph.nodes) degree.set(node.id, 0)\n for (const edge of graph.edges) {\n degree.set(edge.source, (degree.get(edge.source) ?? 0) + 1)\n degree.set(edge.target, (degree.get(edge.target) ?? 0) + 1)\n }\n return degree\n}\n\nexport interface NodeRadiusOptions {\n /** Radius of a degree-0 node. */\n base?: number\n /** Pixels added per unit of degree. */\n perDegree?: number\n /** Hard floor on the radius. */\n min?: number\n /** Hard ceiling on the radius. */\n max?: number\n}\n\n/**\n * Map a node degree to a render radius. Defaults match the values historically\n * used by the React `GraphView`: `clamp(5 + degree * 1.5, 5, 16)`.\n */\nexport function nodeRadius(degree: number, options: NodeRadiusOptions = {}): number {\n const base = options.base ?? 5\n const perDegree = options.perDegree ?? 1.5\n const min = options.min ?? 5\n const max = options.max ?? 16\n return Math.max(min, Math.min(max, base + degree * perDegree))\n}\n","/** Default qualitative palette for community coloring (8 hues). */\nexport const COMMUNITY_COLORS = [\n '#2f6fbb',\n '#d97706',\n '#218838',\n '#7c3aed',\n '#c2410c',\n '#0f766e',\n '#be185d',\n '#4b5563',\n] as const\n\n/** Color used for nodes that are not assigned to any community. */\nexport const UNASSIGNED_COMMUNITY_COLOR = '#64748b'\n\n/**\n * Deterministically assign a color to a community id by hashing the id into the\n * palette. The same id always maps to the same color across runs and hosts.\n * Pass a custom `palette` to theme the output. Matches the historical\n * `GraphView` color assignment.\n */\nexport function colorForCommunity(\n communityId: string | undefined,\n palette: readonly string[] = COMMUNITY_COLORS,\n unassignedColor: string = UNASSIGNED_COMMUNITY_COLOR,\n): string {\n if (!communityId) return unassignedColor\n if (palette.length === 0) return unassignedColor\n let hash = 0\n for (const char of communityId) hash = (hash * 31 + char.charCodeAt(0)) | 0\n return palette[Math.abs(hash) % palette.length]\n}\n","import type { CommunityGraph, GraphNode } from './types.js'\n\nexport interface GraphFilter {\n /** Keep only nodes belonging to these communities (by community id). */\n communityIds?: string[]\n /** Keep only edges whose `kind` is in this set. */\n edgeKinds?: string[]\n /** Explicit allow-list of node ids. Useful for privacy / visibility filtering. */\n nodeIds?: string[]\n /**\n * Arbitrary per-node predicate evaluated against the (allow-listed) nodes.\n * Returning `false` removes the node and any edge touching it. Hosts whose\n * nodes carry extra metadata (privacy class, type, etc.) can filter on it here.\n */\n nodePredicate?: (node: GraphNode) => boolean\n}\n\n/**\n * Produce a new {@link CommunityGraph} containing only the nodes, edges, and\n * community memberships permitted by `filter`. Pure — the input is not mutated.\n *\n * Filtering precedence:\n * 1. `communityIds` (if non-empty) restricts the candidate node set to members\n * of those communities; otherwise `nodeIds` (if given) is the candidate set,\n * else all nodes.\n * 2. `nodePredicate` further narrows the candidate set.\n * 3. Edges survive only when both endpoints survive and `edgeKinds` permits.\n * 4. Empty communities are dropped.\n */\nexport function filterCommunityGraph(\n graph: CommunityGraph | null | undefined,\n filter?: GraphFilter,\n): CommunityGraph {\n if (!graph) return { nodes: [], edges: [], communities: [] }\n\n const nodeIds = new Set(filter?.nodeIds ?? graph.nodes.map((node) => node.id))\n if (filter?.communityIds?.length) {\n const allowedCommunities = new Set(filter.communityIds)\n nodeIds.clear()\n for (const community of graph.communities) {\n if (allowedCommunities.has(community.id)) {\n for (const nodeId of community.nodes) nodeIds.add(nodeId)\n }\n }\n }\n\n const predicate = filter?.nodePredicate\n if (predicate) {\n for (const node of graph.nodes) {\n if (nodeIds.has(node.id) && !predicate(node)) nodeIds.delete(node.id)\n }\n }\n\n const edgeKinds = filter?.edgeKinds ? new Set(filter.edgeKinds) : null\n const nodes = graph.nodes.filter((node) => nodeIds.has(node.id))\n const edges = graph.edges.filter((edge) => (\n nodeIds.has(edge.source)\n && nodeIds.has(edge.target)\n && (!edgeKinds || edgeKinds.has(edge.kind ?? ''))\n ))\n const communities = graph.communities\n .map((community) => ({ ...community, nodes: community.nodes.filter((nodeId) => nodeIds.has(nodeId)) }))\n .filter((community) => community.nodes.length > 0)\n return { nodes, edges, communities }\n}\n","import { computeDegrees } from './degree.js'\nimport type {\n CommunityGraph,\n GraphLayoutAlgorithm,\n PositionedGraph,\n PositionedGraphNode,\n} from './types.js'\n\nexport interface LayoutOptions {\n algorithm?: GraphLayoutAlgorithm\n width?: number\n height?: number\n}\n\nconst DEFAULT_WIDTH = 760\nconst DEFAULT_HEIGHT = 460\n\n/**\n * Deterministically position the nodes of a {@link CommunityGraph} in 2D space.\n *\n * The layout is a closed-form function of (graph, algorithm, width, height) with\n * no randomness or iteration, so the same input always yields the same\n * coordinates — safe for snapshot rendering and server-side generation. Each\n * node is also annotated with its degree and resolved community.\n *\n * Algorithms:\n * - `force` radial ring with a mild degree-based jitter (default)\n * - `radial` plain radial ring at the full layout radius\n * - `community` nodes orbit their community's anchor on the ring\n * - `manual` same ring as `radial`; intended as a base for host-driven pinning\n */\nexport function layoutCommunityGraph(\n graph: CommunityGraph,\n options: LayoutOptions = {},\n): PositionedGraph {\n const algorithm = options.algorithm ?? 'force'\n const width = options.width ?? DEFAULT_WIDTH\n const height = options.height ?? DEFAULT_HEIGHT\n\n const degree = computeDegrees(graph)\n\n const communityByNode = new Map<string, string>()\n for (const community of graph.communities) {\n for (const nodeId of community.nodes) {\n if (!communityByNode.has(nodeId)) communityByNode.set(nodeId, community.id)\n }\n }\n\n const centerX = width / 2\n const centerY = height / 2\n const radius = Math.max(40, Math.min(width, height) * 0.38)\n\n const nodes = graph.nodes.map((node, index): PositionedGraphNode => {\n const angle = (Math.PI * 2 * index) / Math.max(1, graph.nodes.length)\n const communityIndex = graph.communities.findIndex(\n (community) => community.id === communityByNode.get(node.id),\n )\n const communityAngle = (Math.PI * 2 * Math.max(0, communityIndex)) / Math.max(1, graph.communities.length)\n const communityRadius = algorithm === 'community' ? radius * 0.55 : radius\n const localRadius = algorithm === 'force'\n ? radius * (0.7 + ((degree.get(node.id) ?? 0) % 4) * 0.08)\n : radius\n const x = algorithm === 'community'\n ? centerX + Math.cos(communityAngle) * communityRadius + Math.cos(angle) * 46\n : centerX + Math.cos(angle) * localRadius\n const y = algorithm === 'community'\n ? centerY + Math.sin(communityAngle) * communityRadius + Math.sin(angle) * 46\n : centerY + Math.sin(angle) * (algorithm === 'radial' ? radius : localRadius * 0.72)\n return {\n ...node,\n x,\n y,\n degree: degree.get(node.id) ?? 0,\n communityId: communityByNode.get(node.id),\n }\n })\n\n return {\n nodes,\n edges: graph.edges,\n nodeIndex: new Map(nodes.map((node) => [node.id, node])),\n }\n}\n","import type { GraphBounds, PositionedGraphNode, ViewportTransform } from './types.js'\n\nconst EMPTY_BOUNDS: GraphBounds = {\n minX: 0,\n minY: 0,\n maxX: 0,\n maxY: 0,\n width: 0,\n height: 0,\n centerX: 0,\n centerY: 0,\n}\n\n/**\n * Compute the axis-aligned bounding box around a set of positioned nodes.\n * Returns an all-zero box for an empty input.\n */\nexport function computeGraphBounds(\n nodes: ReadonlyArray<Pick<PositionedGraphNode, 'x' | 'y'>>,\n): GraphBounds {\n if (nodes.length === 0) return { ...EMPTY_BOUNDS }\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const node of nodes) {\n if (node.x < minX) minX = node.x\n if (node.y < minY) minY = node.y\n if (node.x > maxX) maxX = node.x\n if (node.y > maxY) maxY = node.y\n }\n return {\n minX,\n minY,\n maxX,\n maxY,\n width: maxX - minX,\n height: maxY - minY,\n centerX: (minX + maxX) / 2,\n centerY: (minY + maxY) / 2,\n }\n}\n\nexport interface FitOptions {\n /** Uniform padding (in viewport units) to leave around the graph. */\n padding?: number\n /** Clamp the computed scale to this minimum. */\n minScale?: number\n /** Clamp the computed scale to this maximum. */\n maxScale?: number\n}\n\n/**\n * Compute a {@link ViewportTransform} (uniform scale + translation) that fits\n * `bounds` centered within a `viewport` of the given width/height. Apply the\n * result as `translate(offsetX, offsetY) scale(scale)` in SVG/canvas space.\n */\nexport function fitGraphToViewport(\n bounds: GraphBounds,\n viewport: { width: number; height: number },\n options: FitOptions = {},\n): ViewportTransform {\n const padding = options.padding ?? 0\n const minScale = options.minScale ?? 0\n const maxScale = options.maxScale ?? Infinity\n\n const availableWidth = Math.max(0, viewport.width - padding * 2)\n const availableHeight = Math.max(0, viewport.height - padding * 2)\n\n let scale = 1\n if (bounds.width > 0 || bounds.height > 0) {\n const scaleX = bounds.width > 0 ? availableWidth / bounds.width : Infinity\n const scaleY = bounds.height > 0 ? availableHeight / bounds.height : Infinity\n scale = Math.min(scaleX, scaleY)\n }\n if (!Number.isFinite(scale) || scale <= 0) scale = 1\n scale = Math.max(minScale, Math.min(maxScale, scale))\n\n const offsetX = viewport.width / 2 - bounds.centerX * scale\n const offsetY = viewport.height / 2 - bounds.centerY * scale\n return { scale, offsetX, offsetY }\n}\n","import { filterCommunityGraph } from './filter.js'\nimport type { CommunityGraph } from './types.js'\n\n/** Build an undirected adjacency map from a graph's edges. */\nexport function buildAdjacency(graph: CommunityGraph): Map<string, Set<string>> {\n const adjacency = new Map<string, Set<string>>()\n const ensure = (id: string) => {\n let set = adjacency.get(id)\n if (!set) {\n set = new Set<string>()\n adjacency.set(id, set)\n }\n return set\n }\n for (const node of graph.nodes) ensure(node.id)\n for (const edge of graph.edges) {\n ensure(edge.source).add(edge.target)\n ensure(edge.target).add(edge.source)\n }\n return adjacency\n}\n\n/** Return the set of node ids directly adjacent to `nodeId` (excludes itself). */\nexport function neighborsOf(graph: CommunityGraph, nodeId: string): Set<string> {\n return new Set(buildAdjacency(graph).get(nodeId) ?? [])\n}\n\nexport interface ExpandOptions {\n /** How many edge-hops to traverse from the seeds. Default 1. */\n depth?: number\n /** Include the seed ids in the result. Default true. */\n includeSeeds?: boolean\n}\n\n/**\n * Breadth-first expansion of one or more seed nodes out to `depth` hops.\n * Returns the set of reached node ids (seeds included by default).\n */\nexport function expandNeighborhood(\n graph: CommunityGraph,\n seeds: Iterable<string>,\n options: ExpandOptions = {},\n): Set<string> {\n const depth = options.depth ?? 1\n const includeSeeds = options.includeSeeds ?? true\n const adjacency = buildAdjacency(graph)\n\n const seen = new Set<string>()\n let frontier = new Set<string>()\n for (const seed of seeds) {\n if (adjacency.has(seed) || depth === 0) {\n seen.add(seed)\n frontier.add(seed)\n }\n }\n\n for (let hop = 0; hop < depth; hop++) {\n const next = new Set<string>()\n for (const id of frontier) {\n for (const neighbor of adjacency.get(id) ?? []) {\n if (!seen.has(neighbor)) {\n seen.add(neighbor)\n next.add(neighbor)\n }\n }\n }\n if (next.size === 0) break\n frontier = next\n }\n\n if (!includeSeeds) {\n for (const seed of seeds) seen.delete(seed)\n }\n return seen\n}\n\n/**\n * Extract the induced subgraph over `nodeIds` — a {@link CommunityGraph}\n * containing only those nodes, the edges between them, and the trimmed\n * community memberships. Thin wrapper over {@link filterCommunityGraph}.\n */\nexport function subgraphForNodes(graph: CommunityGraph, nodeIds: Iterable<string>): CommunityGraph {\n return filterCommunityGraph(graph, { nodeIds: Array.from(nodeIds) })\n}\n\n/**\n * Convenience: the induced subgraph of a seed node plus its `depth`-hop\n * neighborhood. Useful for \"expand selection\" interactions.\n */\nexport function neighborhoodSubgraph(\n graph: CommunityGraph,\n seeds: Iterable<string>,\n options: ExpandOptions = {},\n): CommunityGraph {\n return subgraphForNodes(graph, expandNeighborhood(graph, seeds, options))\n}\n","import type { CommunityGraph, GraphLayoutAlgorithm } from './types.js'\n\n/** Current on-disk version for {@link GraphSnapshot}. */\nexport const GRAPH_SNAPSHOT_VERSION = 1\n\n/**\n * A self-contained, serializable representation of a community graph plus the\n * layout intent used to render it. Designed to be written to a static JSON file\n * and consumed by a JS-only host (e.g. a documentation site) without recomputing\n * the graph.\n */\nexport interface GraphSnapshot {\n version: number\n graph: CommunityGraph\n layout?: {\n algorithm: GraphLayoutAlgorithm\n width: number\n height: number\n }\n generatedAt?: string\n}\n\nexport interface SerializeSnapshotOptions {\n layout?: GraphSnapshot['layout']\n /** ISO timestamp to stamp into the snapshot. Omit for reproducible output. */\n generatedAt?: string\n}\n\nfunction sortedGraph(graph: CommunityGraph): CommunityGraph {\n return {\n nodes: [...graph.nodes].sort((a, b) => a.id.localeCompare(b.id)),\n edges: [...graph.edges].sort((a, b) => (\n a.source.localeCompare(b.source)\n || a.target.localeCompare(b.target)\n || (a.kind ?? '').localeCompare(b.kind ?? '')\n )),\n communities: [...graph.communities]\n .map((community) => ({ ...community, nodes: [...community.nodes].sort() }))\n .sort((a, b) => a.id.localeCompare(b.id)),\n }\n}\n\n/**\n * Build a deterministic {@link GraphSnapshot} from a graph. Nodes, edges, and\n * community members are sorted so that equal graphs serialize identically\n * (stable diffs, content-addressable caching).\n */\nexport function serializeGraphSnapshot(\n graph: CommunityGraph,\n options: SerializeSnapshotOptions = {},\n): GraphSnapshot {\n const snapshot: GraphSnapshot = {\n version: GRAPH_SNAPSHOT_VERSION,\n graph: sortedGraph(graph),\n }\n if (options.layout) snapshot.layout = options.layout\n if (options.generatedAt) snapshot.generatedAt = options.generatedAt\n return snapshot\n}\n\n/** Stable JSON string for a graph snapshot. */\nexport function stringifyGraphSnapshot(\n graph: CommunityGraph,\n options: SerializeSnapshotOptions = {},\n): string {\n return JSON.stringify(serializeGraphSnapshot(graph, options))\n}\n\n/**\n * Validate and unwrap a {@link GraphSnapshot} (or its JSON string) back into a\n * {@link CommunityGraph}. Throws on shape/version mismatch.\n */\nexport function deserializeGraphSnapshot(input: GraphSnapshot | string): CommunityGraph {\n const snapshot: unknown = typeof input === 'string' ? JSON.parse(input) : input\n if (!snapshot || typeof snapshot !== 'object') {\n throw new Error('invalid graph snapshot: not an object')\n }\n const candidate = snapshot as Partial<GraphSnapshot>\n if (candidate.version !== GRAPH_SNAPSHOT_VERSION) {\n throw new Error(`unsupported graph snapshot version: ${String(candidate.version)}`)\n }\n const graph = candidate.graph\n if (\n !graph\n || !Array.isArray(graph.nodes)\n || !Array.isArray(graph.edges)\n || !Array.isArray(graph.communities)\n ) {\n throw new Error('invalid graph snapshot: missing graph nodes/edges/communities')\n }\n return graph\n}\n","// Framework-agnostic graph-source controller.\n//\n// Owns the graph \"source mode\" state machine (citations | topics | precomputed\n// | dynamic-search | user-authored), transition tracking, and the mode→fetch\n// dispatch. The capability previously lived inside the React `useGraphController`\n// hook; it now lives here so JS-only hosts can drive the same logic without\n// React. The React hook is a thin `useSyncExternalStore` adapter over this class.\n//\n// This module depends on @fortemi/core for the repositories and shared types.\n// @fortemi/core never depends on @fortemi/graph — the dependency direction is\n// the linear chain: pglite ← @fortemi/core ← @fortemi/graph ← @fortemi/react.\n\nimport {\n CommunitiesRepository,\n GraphRepository,\n type CommunityCreateInput,\n type CommunityFilterDefinition,\n type CommunityGraph,\n type CommunitySourceDescriptor,\n type DatabaseClient,\n type EmbeddingSetSelector,\n type QueryExecutor,\n type SimilarityGraphResult,\n} from '@fortemi/core'\nimport type { GraphLayoutAlgorithm } from './types.js'\n\n/** A database handle accepted by the underlying repositories. */\nexport type GraphControllerDb = QueryExecutor & DatabaseClient\n\nexport type GraphSourceMode =\n | 'citations'\n | 'topics'\n | 'precomputed'\n | 'dynamic-search'\n | 'user-authored'\n\nexport interface GraphLayoutState {\n algorithm: GraphLayoutAlgorithm\n pinSelectedNodes?: boolean\n preserveViewport?: boolean\n communitySpacing?: number\n}\n\nexport interface GraphTransitionState {\n fromMode?: GraphSourceMode\n toMode: GraphSourceMode\n reason: 'mode-change' | 'embedding-set-change' | 'community-source-change' | 'filter-change' | 'recompute'\n startedAt: string\n}\n\nexport interface GraphControllerStatus {\n loading: boolean\n error: Error | null\n freshness: 'fresh' | 'stale' | 'unknown' | null\n cache: SimilarityGraphResult['cache'] | null\n}\n\nexport type GraphSourceRef = SimilarityGraphResult['graphSource'] | { id: string; name: string }\n\n/** The full, framework-agnostic controller state surfaced to subscribers. */\nexport interface GraphControllerState {\n mode: GraphSourceMode\n graph: CommunityGraph | null\n graphSource?: GraphSourceRef\n communitySource?: CommunitySourceDescriptor\n embeddingSetSelector?: EmbeddingSetSelector\n filters?: CommunityFilterDefinition\n layout: GraphLayoutState\n status: GraphControllerStatus\n transition?: GraphTransitionState\n}\n\nexport interface GraphControllerOptions {\n initialMode?: GraphSourceMode\n initialEmbeddingSetSelector?: EmbeddingSetSelector\n initialCommunitySourceId?: string\n initialFilters?: CommunityFilterDefinition\n layout?: Partial<GraphLayoutState>\n}\n\nconst DEFAULT_LAYOUT: GraphLayoutState = {\n algorithm: 'force',\n preserveViewport: true,\n communitySpacing: 1,\n}\n\nexport type GraphControllerListener = (state: GraphControllerState) => void\n\n/**\n * Drives graph-source selection and loading independent of any UI framework.\n *\n * Construct with pre-built repositories (handy for tests) or via\n * {@link GraphController.fromDb}. Subscribe with {@link subscribe} and read the\n * current state with {@link getState}; call {@link start} once to trigger the\n * initial load. Setters update state and schedule a refresh, mirroring the\n * effect-driven behavior of the original React hook.\n */\nexport class GraphController {\n private readonly graphRepo: GraphRepository\n private readonly communityRepo: CommunitiesRepository\n private readonly listeners = new Set<GraphControllerListener>()\n private communitySourceId: string | null\n private state: GraphControllerState\n\n /** Build a controller from a database handle (production path). */\n static fromDb(db: GraphControllerDb, options: GraphControllerOptions = {}): GraphController {\n return new GraphController(new GraphRepository(db), new CommunitiesRepository(db), options)\n }\n\n constructor(\n graphRepo: GraphRepository,\n communityRepo: CommunitiesRepository,\n options: GraphControllerOptions = {},\n ) {\n this.graphRepo = graphRepo\n this.communityRepo = communityRepo\n this.communitySourceId = options.initialCommunitySourceId ?? null\n this.state = {\n mode: options.initialMode ?? 'citations',\n graph: null,\n graphSource: undefined,\n communitySource: undefined,\n embeddingSetSelector: options.initialEmbeddingSetSelector,\n filters: options.initialFilters,\n layout: { ...DEFAULT_LAYOUT, ...options.layout },\n status: { loading: false, error: null, freshness: null, cache: null },\n transition: undefined,\n }\n }\n\n getState(): GraphControllerState {\n return this.state\n }\n\n subscribe(listener: GraphControllerListener): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /** Run the initial load. Call once after construction (the hook calls this on mount). */\n async start(): Promise<void> {\n await this.refresh()\n }\n\n private setState(patch: Partial<GraphControllerState>): void {\n this.state = { ...this.state, ...patch }\n for (const listener of this.listeners) listener(this.state)\n }\n\n private beginTransition(\n toMode: GraphSourceMode,\n reason: GraphTransitionState['reason'],\n fromMode: GraphSourceMode = this.state.mode,\n ): void {\n this.setState({ transition: { fromMode, toMode, reason, startedAt: new Date().toISOString() } })\n }\n\n async refresh(): Promise<void> {\n try {\n this.setState({ status: { ...this.state.status, loading: true, error: null } })\n const sources = await this.communityRepo.listCommunitySources()\n const activeCommunity = this.communitySourceId\n ? sources.find((source) => source.id === this.communitySourceId)\n : undefined\n this.setState({ communitySource: activeCommunity })\n\n const mode = this.state.mode\n\n if (mode === 'citations') {\n const nextGraph = await this.graphRepo.buildLinkGraph()\n this.setState({\n graph: nextGraph,\n graphSource: { id: 'citations', name: 'Citation graph' },\n status: { loading: false, error: null, freshness: 'fresh', cache: null },\n })\n return\n }\n\n if (mode === 'topics') {\n const selector = this.state.embeddingSetSelector\n if (!selector) throw new Error('topics mode requires an embedding-set selector')\n const result = await this.graphRepo.buildOrLoadSimilarityGraph({ selector })\n this.setState({\n graph: result.graph,\n graphSource: result.graphSource,\n status: { loading: false, error: null, freshness: result.freshness, cache: result.cache },\n })\n return\n }\n\n if (mode === 'precomputed') {\n if (!activeCommunity?.graphSourceId) {\n throw new Error('precomputed mode requires a community source with graphSourceId')\n }\n const assignments = await this.communityRepo.getCommunityAssignments(activeCommunity.id)\n const nextGraph = await this.graphRepo.loadGraphArtifact(\n activeCommunity.graphSourceId,\n assignments.map((assignment) => assignment.noteId),\n )\n this.setState({\n graph: nextGraph,\n graphSource: { id: activeCommunity.graphSourceId, name: activeCommunity.name },\n status: { loading: false, error: null, freshness: activeCommunity.freshness ?? 'unknown', cache: null },\n })\n return\n }\n\n if (mode === 'dynamic-search') {\n const assignments = await this.communityRepo.previewDynamicCommunity(this.state.filters ?? {})\n this.setState({\n graph: {\n nodes: assignments.map((assignment) => ({ id: assignment.noteId })),\n edges: [],\n communities: [{ id: 'dynamic-preview', nodes: assignments.map((assignment) => assignment.noteId) }],\n },\n graphSource: { id: 'dynamic-preview', name: 'Dynamic preview' },\n status: { loading: false, error: null, freshness: 'unknown', cache: null },\n })\n return\n }\n\n if (mode === 'user-authored') {\n if (!activeCommunity) throw new Error('user-authored mode requires an active community source')\n const assignments = await this.communityRepo.getCommunityAssignments(activeCommunity.id)\n this.setState({\n graph: {\n nodes: assignments.map((assignment) => ({ id: assignment.noteId })),\n edges: [],\n communities: [{ id: activeCommunity.id, nodes: assignments.map((assignment) => assignment.noteId) }],\n },\n graphSource: { id: activeCommunity.graphSourceId ?? activeCommunity.id, name: activeCommunity.name },\n status: { loading: false, error: null, freshness: activeCommunity.freshness ?? 'unknown', cache: null },\n })\n }\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err))\n this.setState({ status: { ...this.state.status, loading: false, error: e } })\n throw e\n }\n }\n\n setMode(nextMode: GraphSourceMode): void {\n this.beginTransition(nextMode, 'mode-change')\n this.setState({ mode: nextMode })\n void this.refresh()\n }\n\n setEmbeddingSetSelector(selector: EmbeddingSetSelector): void {\n this.beginTransition('topics', 'embedding-set-change')\n this.setState({ embeddingSetSelector: selector, mode: 'topics' })\n void this.refresh()\n }\n\n setCommunitySource(sourceId: string | null): void {\n this.communitySourceId = sourceId\n this.beginTransition(this.state.mode, 'community-source-change')\n void this.refresh()\n }\n\n /** Apply dynamic-search filters and switch into that mode (state only). */\n private applyFilters(nextFilters: CommunityFilterDefinition): void {\n this.beginTransition('dynamic-search', 'filter-change')\n this.setState({ filters: nextFilters, mode: 'dynamic-search' })\n }\n\n setFilters(nextFilters: CommunityFilterDefinition): void {\n this.applyFilters(nextFilters)\n void this.refresh()\n }\n\n async recompute(): Promise<void> {\n this.beginTransition(this.state.mode, 'recompute')\n if (this.state.mode === 'topics' && this.state.embeddingSetSelector) {\n const result = await this.graphRepo.buildOrLoadSimilarityGraph({\n selector: this.state.embeddingSetSelector,\n source: 'live-only',\n })\n this.setState({\n graph: result.graph,\n graphSource: result.graphSource,\n status: { loading: false, error: null, freshness: result.freshness, cache: result.cache },\n })\n return\n }\n await this.refresh()\n }\n\n async previewDynamicCommunity(nextFilters: CommunityFilterDefinition): Promise<void> {\n this.applyFilters(nextFilters)\n await this.refresh()\n }\n\n async saveCurrentCommunity(input: CommunityCreateInput): Promise<CommunitySourceDescriptor> {\n const source = await this.communityRepo.saveCommunity(input)\n this.communitySourceId = source.id\n this.setState({ communitySource: source })\n return source\n }\n}\n","// @fortemi/graph — framework-agnostic graph presentation/projection helpers\n// plus the graph-source controller.\n//\n// Depends on @fortemi/core (the base data layer) and is consumed by\n// @fortemi/react and JS-only hosts. Dependency direction is the linear chain:\n// pglite ← @fortemi/core ← @fortemi/graph ← @fortemi/react. @fortemi/core never\n// imports @fortemi/graph.\n//\n// Pure projection helpers (layout, filtering, coloring, sizing, bounds,\n// neighborhood, snapshot) give React and JS-only hosts the shared logic to\n// render their own SVG/canvas views. `GraphController` adds the framework-\n// agnostic graph-source state machine (mode selection + load dispatch) on top\n// of @fortemi/core's repositories.\n//\n// Community *detection* intentionally lives in @fortemi/core (the base layer);\n// this package only renders/projects graphs it is given and orchestrates which\n// source to load.\n\nexport const VERSION = '2026.6.4'\n\nexport type {\n GraphNode,\n GraphEdge,\n GraphCommunity,\n CommunityGraph,\n GraphLayoutAlgorithm,\n PositionedGraphNode,\n PositionedGraph,\n GraphBounds,\n ViewportTransform,\n} from './types.js'\n\nexport { computeDegrees, nodeRadius } from './degree.js'\nexport type { NodeRadiusOptions } from './degree.js'\n\nexport { COMMUNITY_COLORS, UNASSIGNED_COMMUNITY_COLOR, colorForCommunity } from './color.js'\n\nexport { filterCommunityGraph } from './filter.js'\nexport type { GraphFilter } from './filter.js'\n\nexport { layoutCommunityGraph } from './layout.js'\nexport type { LayoutOptions } from './layout.js'\n\nexport { computeGraphBounds, fitGraphToViewport } from './bounds.js'\nexport type { FitOptions } from './bounds.js'\n\nexport {\n buildAdjacency,\n neighborsOf,\n expandNeighborhood,\n subgraphForNodes,\n neighborhoodSubgraph,\n} from './neighborhood.js'\nexport type { ExpandOptions } from './neighborhood.js'\n\nexport {\n GRAPH_SNAPSHOT_VERSION,\n serializeGraphSnapshot,\n stringifyGraphSnapshot,\n deserializeGraphSnapshot,\n} from './serialize.js'\nexport type { GraphSnapshot, SerializeSnapshotOptions } from './serialize.js'\n\nexport { GraphController } from './controller.js'\nexport type {\n GraphControllerDb,\n GraphSourceMode,\n GraphLayoutState,\n GraphTransitionState,\n GraphControllerStatus,\n GraphSourceRef,\n GraphControllerState,\n GraphControllerOptions,\n GraphControllerListener,\n} from './controller.js'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/degree.ts","../src/color.ts","../src/filter.ts","../src/layout.ts","../src/bounds.ts","../src/neighborhood.ts","../src/serialize.ts","../src/controller.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { CommunityGraph } from './types.js'\n\n/** Count the (undirected) degree of every node, including isolated nodes (0). */\nexport function computeDegrees(graph: CommunityGraph): Map<string, number> {\n const degree = new Map<string, number>()\n for (const node of graph.nodes) degree.set(node.id, 0)\n for (const edge of graph.edges) {\n degree.set(edge.source, (degree.get(edge.source) ?? 0) + 1)\n degree.set(edge.target, (degree.get(edge.target) ?? 0) + 1)\n }\n return degree\n}\n\nexport interface NodeRadiusOptions {\n /** Radius of a degree-0 node. */\n base?: number\n /** Pixels added per unit of degree. */\n perDegree?: number\n /** Hard floor on the radius. */\n min?: number\n /** Hard ceiling on the radius. */\n max?: number\n}\n\n/**\n * Map a node degree to a render radius. Defaults match the values historically\n * used by the React `GraphView`: `clamp(5 + degree * 1.5, 5, 16)`.\n */\nexport function nodeRadius(degree: number, options: NodeRadiusOptions = {}): number {\n const base = options.base ?? 5\n const perDegree = options.perDegree ?? 1.5\n const min = options.min ?? 5\n const max = options.max ?? 16\n return Math.max(min, Math.min(max, base + degree * perDegree))\n}\n","/** Default qualitative palette for community coloring (8 hues). */\nexport const COMMUNITY_COLORS = [\n '#2f6fbb',\n '#d97706',\n '#218838',\n '#7c3aed',\n '#c2410c',\n '#0f766e',\n '#be185d',\n '#4b5563',\n] as const\n\n/** Color used for nodes that are not assigned to any community. */\nexport const UNASSIGNED_COMMUNITY_COLOR = '#64748b'\n\n/**\n * Deterministically assign a color to a community id by hashing the id into the\n * palette. The same id always maps to the same color across runs and hosts.\n * Pass a custom `palette` to theme the output. Matches the historical\n * `GraphView` color assignment.\n */\nexport function colorForCommunity(\n communityId: string | undefined,\n palette: readonly string[] = COMMUNITY_COLORS,\n unassignedColor: string = UNASSIGNED_COMMUNITY_COLOR,\n): string {\n if (!communityId) return unassignedColor\n if (palette.length === 0) return unassignedColor\n let hash = 0\n for (const char of communityId) hash = (hash * 31 + char.charCodeAt(0)) | 0\n return palette[Math.abs(hash) % palette.length]\n}\n","import type { CommunityGraph, GraphNode } from './types.js'\n\nexport interface GraphFilter {\n /** Keep only nodes belonging to these communities (by community id). */\n communityIds?: string[]\n /** Keep only edges whose `kind` is in this set. */\n edgeKinds?: string[]\n /** Explicit allow-list of node ids. Useful for privacy / visibility filtering. */\n nodeIds?: string[]\n /**\n * Arbitrary per-node predicate evaluated against the (allow-listed) nodes.\n * Returning `false` removes the node and any edge touching it. Hosts whose\n * nodes carry extra metadata (privacy class, type, etc.) can filter on it here.\n */\n nodePredicate?: (node: GraphNode) => boolean\n}\n\n/**\n * Produce a new {@link CommunityGraph} containing only the nodes, edges, and\n * community memberships permitted by `filter`. Pure — the input is not mutated.\n *\n * Filtering precedence:\n * 1. `communityIds` (if non-empty) restricts the candidate node set to members\n * of those communities; otherwise `nodeIds` (if given) is the candidate set,\n * else all nodes.\n * 2. `nodePredicate` further narrows the candidate set.\n * 3. Edges survive only when both endpoints survive and `edgeKinds` permits.\n * 4. Empty communities are dropped.\n */\nexport function filterCommunityGraph(\n graph: CommunityGraph | null | undefined,\n filter?: GraphFilter,\n): CommunityGraph {\n if (!graph) return { nodes: [], edges: [], communities: [] }\n\n const nodeIds = new Set(filter?.nodeIds ?? graph.nodes.map((node) => node.id))\n if (filter?.communityIds?.length) {\n const allowedCommunities = new Set(filter.communityIds)\n nodeIds.clear()\n for (const community of graph.communities) {\n if (allowedCommunities.has(community.id)) {\n for (const nodeId of community.nodes) nodeIds.add(nodeId)\n }\n }\n }\n\n const predicate = filter?.nodePredicate\n if (predicate) {\n for (const node of graph.nodes) {\n if (nodeIds.has(node.id) && !predicate(node)) nodeIds.delete(node.id)\n }\n }\n\n const edgeKinds = filter?.edgeKinds ? new Set(filter.edgeKinds) : null\n const nodes = graph.nodes.filter((node) => nodeIds.has(node.id))\n const edges = graph.edges.filter((edge) => (\n nodeIds.has(edge.source)\n && nodeIds.has(edge.target)\n && (!edgeKinds || edgeKinds.has(edge.kind ?? ''))\n ))\n const communities = graph.communities\n .map((community) => ({ ...community, nodes: community.nodes.filter((nodeId) => nodeIds.has(nodeId)) }))\n .filter((community) => community.nodes.length > 0)\n return { nodes, edges, communities }\n}\n","import { computeDegrees } from './degree.js'\nimport type {\n CommunityGraph,\n GraphLayoutAlgorithm,\n PositionedGraph,\n PositionedGraphNode,\n} from './types.js'\n\nexport interface LayoutOptions {\n algorithm?: GraphLayoutAlgorithm\n width?: number\n height?: number\n}\n\nconst DEFAULT_WIDTH = 760\nconst DEFAULT_HEIGHT = 460\n\n/**\n * Deterministically position the nodes of a {@link CommunityGraph} in 2D space.\n *\n * The layout is a closed-form function of (graph, algorithm, width, height) with\n * no randomness or iteration, so the same input always yields the same\n * coordinates — safe for snapshot rendering and server-side generation. Each\n * node is also annotated with its degree and resolved community.\n *\n * Algorithms:\n * - `force` radial ring with a mild degree-based jitter (default)\n * - `radial` plain radial ring at the full layout radius\n * - `community` nodes orbit their community's anchor on the ring\n * - `manual` same ring as `radial`; intended as a base for host-driven pinning\n */\nexport function layoutCommunityGraph(\n graph: CommunityGraph,\n options: LayoutOptions = {},\n): PositionedGraph {\n const algorithm = options.algorithm ?? 'force'\n const width = options.width ?? DEFAULT_WIDTH\n const height = options.height ?? DEFAULT_HEIGHT\n\n const degree = computeDegrees(graph)\n\n const communityByNode = new Map<string, string>()\n for (const community of graph.communities) {\n for (const nodeId of community.nodes) {\n if (!communityByNode.has(nodeId)) communityByNode.set(nodeId, community.id)\n }\n }\n\n const centerX = width / 2\n const centerY = height / 2\n const radius = Math.max(40, Math.min(width, height) * 0.38)\n\n const nodes = graph.nodes.map((node, index): PositionedGraphNode => {\n const angle = (Math.PI * 2 * index) / Math.max(1, graph.nodes.length)\n const communityIndex = graph.communities.findIndex(\n (community) => community.id === communityByNode.get(node.id),\n )\n const communityAngle = (Math.PI * 2 * Math.max(0, communityIndex)) / Math.max(1, graph.communities.length)\n const communityRadius = algorithm === 'community' ? radius * 0.55 : radius\n const localRadius = algorithm === 'force'\n ? radius * (0.7 + ((degree.get(node.id) ?? 0) % 4) * 0.08)\n : radius\n const x = algorithm === 'community'\n ? centerX + Math.cos(communityAngle) * communityRadius + Math.cos(angle) * 46\n : centerX + Math.cos(angle) * localRadius\n const y = algorithm === 'community'\n ? centerY + Math.sin(communityAngle) * communityRadius + Math.sin(angle) * 46\n : centerY + Math.sin(angle) * (algorithm === 'radial' ? radius : localRadius * 0.72)\n return {\n ...node,\n x,\n y,\n degree: degree.get(node.id) ?? 0,\n communityId: communityByNode.get(node.id),\n }\n })\n\n return {\n nodes,\n edges: graph.edges,\n nodeIndex: new Map(nodes.map((node) => [node.id, node])),\n }\n}\n","import type { GraphBounds, PositionedGraphNode, ViewportTransform } from './types.js'\n\nconst EMPTY_BOUNDS: GraphBounds = {\n minX: 0,\n minY: 0,\n maxX: 0,\n maxY: 0,\n width: 0,\n height: 0,\n centerX: 0,\n centerY: 0,\n}\n\n/**\n * Compute the axis-aligned bounding box around a set of positioned nodes.\n * Returns an all-zero box for an empty input.\n */\nexport function computeGraphBounds(\n nodes: ReadonlyArray<Pick<PositionedGraphNode, 'x' | 'y'>>,\n): GraphBounds {\n if (nodes.length === 0) return { ...EMPTY_BOUNDS }\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const node of nodes) {\n if (node.x < minX) minX = node.x\n if (node.y < minY) minY = node.y\n if (node.x > maxX) maxX = node.x\n if (node.y > maxY) maxY = node.y\n }\n return {\n minX,\n minY,\n maxX,\n maxY,\n width: maxX - minX,\n height: maxY - minY,\n centerX: (minX + maxX) / 2,\n centerY: (minY + maxY) / 2,\n }\n}\n\nexport interface FitOptions {\n /** Uniform padding (in viewport units) to leave around the graph. */\n padding?: number\n /** Clamp the computed scale to this minimum. */\n minScale?: number\n /** Clamp the computed scale to this maximum. */\n maxScale?: number\n}\n\n/**\n * Compute a {@link ViewportTransform} (uniform scale + translation) that fits\n * `bounds` centered within a `viewport` of the given width/height. Apply the\n * result as `translate(offsetX, offsetY) scale(scale)` in SVG/canvas space.\n */\nexport function fitGraphToViewport(\n bounds: GraphBounds,\n viewport: { width: number; height: number },\n options: FitOptions = {},\n): ViewportTransform {\n const padding = options.padding ?? 0\n const minScale = options.minScale ?? 0\n const maxScale = options.maxScale ?? Infinity\n\n const availableWidth = Math.max(0, viewport.width - padding * 2)\n const availableHeight = Math.max(0, viewport.height - padding * 2)\n\n let scale = 1\n if (bounds.width > 0 || bounds.height > 0) {\n const scaleX = bounds.width > 0 ? availableWidth / bounds.width : Infinity\n const scaleY = bounds.height > 0 ? availableHeight / bounds.height : Infinity\n scale = Math.min(scaleX, scaleY)\n }\n if (!Number.isFinite(scale) || scale <= 0) scale = 1\n scale = Math.max(minScale, Math.min(maxScale, scale))\n\n const offsetX = viewport.width / 2 - bounds.centerX * scale\n const offsetY = viewport.height / 2 - bounds.centerY * scale\n return { scale, offsetX, offsetY }\n}\n","import { filterCommunityGraph } from './filter.js'\nimport type { CommunityGraph } from './types.js'\n\n/** Build an undirected adjacency map from a graph's edges. */\nexport function buildAdjacency(graph: CommunityGraph): Map<string, Set<string>> {\n const adjacency = new Map<string, Set<string>>()\n const ensure = (id: string) => {\n let set = adjacency.get(id)\n if (!set) {\n set = new Set<string>()\n adjacency.set(id, set)\n }\n return set\n }\n for (const node of graph.nodes) ensure(node.id)\n for (const edge of graph.edges) {\n ensure(edge.source).add(edge.target)\n ensure(edge.target).add(edge.source)\n }\n return adjacency\n}\n\n/** Return the set of node ids directly adjacent to `nodeId` (excludes itself). */\nexport function neighborsOf(graph: CommunityGraph, nodeId: string): Set<string> {\n return new Set(buildAdjacency(graph).get(nodeId) ?? [])\n}\n\nexport interface ExpandOptions {\n /** How many edge-hops to traverse from the seeds. Default 1. */\n depth?: number\n /** Include the seed ids in the result. Default true. */\n includeSeeds?: boolean\n}\n\n/**\n * Breadth-first expansion of one or more seed nodes out to `depth` hops.\n * Returns the set of reached node ids (seeds included by default).\n */\nexport function expandNeighborhood(\n graph: CommunityGraph,\n seeds: Iterable<string>,\n options: ExpandOptions = {},\n): Set<string> {\n const depth = options.depth ?? 1\n const includeSeeds = options.includeSeeds ?? true\n const adjacency = buildAdjacency(graph)\n\n const seen = new Set<string>()\n let frontier = new Set<string>()\n for (const seed of seeds) {\n if (adjacency.has(seed) || depth === 0) {\n seen.add(seed)\n frontier.add(seed)\n }\n }\n\n for (let hop = 0; hop < depth; hop++) {\n const next = new Set<string>()\n for (const id of frontier) {\n for (const neighbor of adjacency.get(id) ?? []) {\n if (!seen.has(neighbor)) {\n seen.add(neighbor)\n next.add(neighbor)\n }\n }\n }\n if (next.size === 0) break\n frontier = next\n }\n\n if (!includeSeeds) {\n for (const seed of seeds) seen.delete(seed)\n }\n return seen\n}\n\n/**\n * Extract the induced subgraph over `nodeIds` — a {@link CommunityGraph}\n * containing only those nodes, the edges between them, and the trimmed\n * community memberships. Thin wrapper over {@link filterCommunityGraph}.\n */\nexport function subgraphForNodes(graph: CommunityGraph, nodeIds: Iterable<string>): CommunityGraph {\n return filterCommunityGraph(graph, { nodeIds: Array.from(nodeIds) })\n}\n\n/**\n * Convenience: the induced subgraph of a seed node plus its `depth`-hop\n * neighborhood. Useful for \"expand selection\" interactions.\n */\nexport function neighborhoodSubgraph(\n graph: CommunityGraph,\n seeds: Iterable<string>,\n options: ExpandOptions = {},\n): CommunityGraph {\n return subgraphForNodes(graph, expandNeighborhood(graph, seeds, options))\n}\n","import type { CommunityGraph, GraphLayoutAlgorithm } from './types.js'\n\n/** Current on-disk version for {@link GraphSnapshot}. */\nexport const GRAPH_SNAPSHOT_VERSION = 1\n\n/**\n * A self-contained, serializable representation of a community graph plus the\n * layout intent used to render it. Designed to be written to a static JSON file\n * and consumed by a JS-only host (e.g. a documentation site) without recomputing\n * the graph.\n */\nexport interface GraphSnapshot {\n version: number\n graph: CommunityGraph\n layout?: {\n algorithm: GraphLayoutAlgorithm\n width: number\n height: number\n }\n generatedAt?: string\n}\n\nexport interface SerializeSnapshotOptions {\n layout?: GraphSnapshot['layout']\n /** ISO timestamp to stamp into the snapshot. Omit for reproducible output. */\n generatedAt?: string\n}\n\nfunction sortedGraph(graph: CommunityGraph): CommunityGraph {\n return {\n nodes: [...graph.nodes].sort((a, b) => a.id.localeCompare(b.id)),\n edges: [...graph.edges].sort((a, b) => (\n a.source.localeCompare(b.source)\n || a.target.localeCompare(b.target)\n || (a.kind ?? '').localeCompare(b.kind ?? '')\n )),\n communities: [...graph.communities]\n .map((community) => ({ ...community, nodes: [...community.nodes].sort() }))\n .sort((a, b) => a.id.localeCompare(b.id)),\n }\n}\n\n/**\n * Build a deterministic {@link GraphSnapshot} from a graph. Nodes, edges, and\n * community members are sorted so that equal graphs serialize identically\n * (stable diffs, content-addressable caching).\n */\nexport function serializeGraphSnapshot(\n graph: CommunityGraph,\n options: SerializeSnapshotOptions = {},\n): GraphSnapshot {\n const snapshot: GraphSnapshot = {\n version: GRAPH_SNAPSHOT_VERSION,\n graph: sortedGraph(graph),\n }\n if (options.layout) snapshot.layout = options.layout\n if (options.generatedAt) snapshot.generatedAt = options.generatedAt\n return snapshot\n}\n\n/** Stable JSON string for a graph snapshot. */\nexport function stringifyGraphSnapshot(\n graph: CommunityGraph,\n options: SerializeSnapshotOptions = {},\n): string {\n return JSON.stringify(serializeGraphSnapshot(graph, options))\n}\n\n/**\n * Validate and unwrap a {@link GraphSnapshot} (or its JSON string) back into a\n * {@link CommunityGraph}. Throws on shape/version mismatch.\n */\nexport function deserializeGraphSnapshot(input: GraphSnapshot | string): CommunityGraph {\n const snapshot: unknown = typeof input === 'string' ? JSON.parse(input) : input\n if (!snapshot || typeof snapshot !== 'object') {\n throw new Error('invalid graph snapshot: not an object')\n }\n const candidate = snapshot as Partial<GraphSnapshot>\n if (candidate.version !== GRAPH_SNAPSHOT_VERSION) {\n throw new Error(`unsupported graph snapshot version: ${String(candidate.version)}`)\n }\n const graph = candidate.graph\n if (\n !graph\n || !Array.isArray(graph.nodes)\n || !Array.isArray(graph.edges)\n || !Array.isArray(graph.communities)\n ) {\n throw new Error('invalid graph snapshot: missing graph nodes/edges/communities')\n }\n return graph\n}\n","// Framework-agnostic graph-source controller.\n//\n// Owns the graph \"source mode\" state machine (citations | topics | precomputed\n// | dynamic-search | user-authored), transition tracking, and the mode→fetch\n// dispatch. The capability previously lived inside the React `useGraphController`\n// hook; it now lives here so JS-only hosts can drive the same logic without\n// React. The React hook is a thin `useSyncExternalStore` adapter over this class.\n//\n// This module depends on @fortemi/core for the repositories and shared types.\n// @fortemi/core never depends on @fortemi/graph — the dependency direction is\n// the linear chain: pglite ← @fortemi/core ← @fortemi/graph ← @fortemi/react.\n\nimport {\n CommunitiesRepository,\n GraphRepository,\n type CommunityCreateInput,\n type CommunityFilterDefinition,\n type CommunityGraph,\n type CommunitySourceDescriptor,\n type DatabaseClient,\n type EmbeddingSetSelector,\n type QueryExecutor,\n type SimilarityGraphResult,\n} from '@fortemi/core'\nimport type { GraphLayoutAlgorithm } from './types.js'\n\n/** A database handle accepted by the underlying repositories. */\nexport type GraphControllerDb = QueryExecutor & DatabaseClient\n\nexport type GraphSourceMode =\n | 'citations'\n | 'topics'\n | 'precomputed'\n | 'dynamic-search'\n | 'user-authored'\n\nexport interface GraphLayoutState {\n algorithm: GraphLayoutAlgorithm\n pinSelectedNodes?: boolean\n preserveViewport?: boolean\n communitySpacing?: number\n}\n\nexport interface GraphTransitionState {\n fromMode?: GraphSourceMode\n toMode: GraphSourceMode\n reason: 'mode-change' | 'embedding-set-change' | 'community-source-change' | 'filter-change' | 'recompute'\n startedAt: string\n}\n\nexport interface GraphControllerStatus {\n loading: boolean\n error: Error | null\n freshness: 'fresh' | 'stale' | 'unknown' | null\n cache: SimilarityGraphResult['cache'] | null\n}\n\nexport type GraphSourceRef = SimilarityGraphResult['graphSource'] | { id: string; name: string }\n\n/** The full, framework-agnostic controller state surfaced to subscribers. */\nexport interface GraphControllerState {\n mode: GraphSourceMode\n graph: CommunityGraph | null\n graphSource?: GraphSourceRef\n communitySource?: CommunitySourceDescriptor\n embeddingSetSelector?: EmbeddingSetSelector\n filters?: CommunityFilterDefinition\n layout: GraphLayoutState\n status: GraphControllerStatus\n transition?: GraphTransitionState\n}\n\nexport interface GraphControllerOptions {\n initialMode?: GraphSourceMode\n initialEmbeddingSetSelector?: EmbeddingSetSelector\n initialCommunitySourceId?: string\n initialFilters?: CommunityFilterDefinition\n layout?: Partial<GraphLayoutState>\n}\n\nconst DEFAULT_LAYOUT: GraphLayoutState = {\n algorithm: 'force',\n preserveViewport: true,\n communitySpacing: 1,\n}\n\nexport type GraphControllerListener = (state: GraphControllerState) => void\n\n/**\n * Drives graph-source selection and loading independent of any UI framework.\n *\n * Construct with pre-built repositories (handy for tests) or via\n * {@link GraphController.fromDb}. Subscribe with {@link subscribe} and read the\n * current state with {@link getState}; call {@link start} once to trigger the\n * initial load. Setters update state and schedule a refresh, mirroring the\n * effect-driven behavior of the original React hook.\n */\nexport class GraphController {\n private readonly graphRepo: GraphRepository\n private readonly communityRepo: CommunitiesRepository\n private readonly listeners = new Set<GraphControllerListener>()\n private communitySourceId: string | null\n private state: GraphControllerState\n\n /** Build a controller from a database handle (production path). */\n static fromDb(db: GraphControllerDb, options: GraphControllerOptions = {}): GraphController {\n return new GraphController(new GraphRepository(db), new CommunitiesRepository(db), options)\n }\n\n constructor(\n graphRepo: GraphRepository,\n communityRepo: CommunitiesRepository,\n options: GraphControllerOptions = {},\n ) {\n this.graphRepo = graphRepo\n this.communityRepo = communityRepo\n this.communitySourceId = options.initialCommunitySourceId ?? null\n this.state = {\n mode: options.initialMode ?? 'citations',\n graph: null,\n graphSource: undefined,\n communitySource: undefined,\n embeddingSetSelector: options.initialEmbeddingSetSelector,\n filters: options.initialFilters,\n layout: { ...DEFAULT_LAYOUT, ...options.layout },\n status: { loading: false, error: null, freshness: null, cache: null },\n transition: undefined,\n }\n }\n\n getState(): GraphControllerState {\n return this.state\n }\n\n subscribe(listener: GraphControllerListener): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /** Run the initial load. Call once after construction (the hook calls this on mount). */\n async start(): Promise<void> {\n await this.refresh()\n }\n\n private setState(patch: Partial<GraphControllerState>): void {\n this.state = { ...this.state, ...patch }\n for (const listener of this.listeners) listener(this.state)\n }\n\n private beginTransition(\n toMode: GraphSourceMode,\n reason: GraphTransitionState['reason'],\n fromMode: GraphSourceMode = this.state.mode,\n ): void {\n this.setState({ transition: { fromMode, toMode, reason, startedAt: new Date().toISOString() } })\n }\n\n async refresh(): Promise<void> {\n try {\n this.setState({ status: { ...this.state.status, loading: true, error: null } })\n const sources = await this.communityRepo.listCommunitySources()\n const activeCommunity = this.communitySourceId\n ? sources.find((source) => source.id === this.communitySourceId)\n : undefined\n this.setState({ communitySource: activeCommunity })\n\n const mode = this.state.mode\n\n if (mode === 'citations') {\n const nextGraph = await this.graphRepo.buildLinkGraph()\n this.setState({\n graph: nextGraph,\n graphSource: { id: 'citations', name: 'Citation graph' },\n status: { loading: false, error: null, freshness: 'fresh', cache: null },\n })\n return\n }\n\n if (mode === 'topics') {\n const selector = this.state.embeddingSetSelector\n if (!selector) throw new Error('topics mode requires an embedding-set selector')\n const result = await this.graphRepo.buildOrLoadSimilarityGraph({ selector })\n this.setState({\n graph: result.graph,\n graphSource: result.graphSource,\n status: { loading: false, error: null, freshness: result.freshness, cache: result.cache },\n })\n return\n }\n\n if (mode === 'precomputed') {\n if (!activeCommunity?.graphSourceId) {\n throw new Error('precomputed mode requires a community source with graphSourceId')\n }\n const assignments = await this.communityRepo.getCommunityAssignments(activeCommunity.id)\n const nextGraph = await this.graphRepo.loadGraphArtifact(\n activeCommunity.graphSourceId,\n assignments.map((assignment) => assignment.noteId),\n )\n this.setState({\n graph: nextGraph,\n graphSource: { id: activeCommunity.graphSourceId, name: activeCommunity.name },\n status: { loading: false, error: null, freshness: activeCommunity.freshness ?? 'unknown', cache: null },\n })\n return\n }\n\n if (mode === 'dynamic-search') {\n const assignments = await this.communityRepo.previewDynamicCommunity(this.state.filters ?? {})\n this.setState({\n graph: {\n nodes: assignments.map((assignment) => ({ id: assignment.noteId })),\n edges: [],\n communities: [{ id: 'dynamic-preview', nodes: assignments.map((assignment) => assignment.noteId) }],\n },\n graphSource: { id: 'dynamic-preview', name: 'Dynamic preview' },\n status: { loading: false, error: null, freshness: 'unknown', cache: null },\n })\n return\n }\n\n if (mode === 'user-authored') {\n if (!activeCommunity) throw new Error('user-authored mode requires an active community source')\n const assignments = await this.communityRepo.getCommunityAssignments(activeCommunity.id)\n this.setState({\n graph: {\n nodes: assignments.map((assignment) => ({ id: assignment.noteId })),\n edges: [],\n communities: [{ id: activeCommunity.id, nodes: assignments.map((assignment) => assignment.noteId) }],\n },\n graphSource: { id: activeCommunity.graphSourceId ?? activeCommunity.id, name: activeCommunity.name },\n status: { loading: false, error: null, freshness: activeCommunity.freshness ?? 'unknown', cache: null },\n })\n }\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err))\n this.setState({ status: { ...this.state.status, loading: false, error: e } })\n throw e\n }\n }\n\n setMode(nextMode: GraphSourceMode): void {\n this.beginTransition(nextMode, 'mode-change')\n this.setState({ mode: nextMode })\n void this.refresh()\n }\n\n setEmbeddingSetSelector(selector: EmbeddingSetSelector): void {\n this.beginTransition('topics', 'embedding-set-change')\n this.setState({ embeddingSetSelector: selector, mode: 'topics' })\n void this.refresh()\n }\n\n setCommunitySource(sourceId: string | null): void {\n this.communitySourceId = sourceId\n this.beginTransition(this.state.mode, 'community-source-change')\n void this.refresh()\n }\n\n /** Apply dynamic-search filters and switch into that mode (state only). */\n private applyFilters(nextFilters: CommunityFilterDefinition): void {\n this.beginTransition('dynamic-search', 'filter-change')\n this.setState({ filters: nextFilters, mode: 'dynamic-search' })\n }\n\n setFilters(nextFilters: CommunityFilterDefinition): void {\n this.applyFilters(nextFilters)\n void this.refresh()\n }\n\n async recompute(): Promise<void> {\n this.beginTransition(this.state.mode, 'recompute')\n if (this.state.mode === 'topics' && this.state.embeddingSetSelector) {\n const result = await this.graphRepo.buildOrLoadSimilarityGraph({\n selector: this.state.embeddingSetSelector,\n source: 'live-only',\n })\n this.setState({\n graph: result.graph,\n graphSource: result.graphSource,\n status: { loading: false, error: null, freshness: result.freshness, cache: result.cache },\n })\n return\n }\n await this.refresh()\n }\n\n async previewDynamicCommunity(nextFilters: CommunityFilterDefinition): Promise<void> {\n this.applyFilters(nextFilters)\n await this.refresh()\n }\n\n async saveCurrentCommunity(input: CommunityCreateInput): Promise<CommunitySourceDescriptor> {\n const source = await this.communityRepo.saveCommunity(input)\n this.communitySourceId = source.id\n this.setState({ communitySource: source })\n return source\n }\n}\n","// @fortemi/graph — framework-agnostic graph presentation/projection helpers\n// plus the graph-source controller.\n//\n// Depends on @fortemi/core (the base data layer) and is consumed by\n// @fortemi/react and JS-only hosts. Dependency direction is the linear chain:\n// pglite ← @fortemi/core ← @fortemi/graph ← @fortemi/react. @fortemi/core never\n// imports @fortemi/graph.\n//\n// Pure projection helpers (layout, filtering, coloring, sizing, bounds,\n// neighborhood, snapshot) give React and JS-only hosts the shared logic to\n// render their own SVG/canvas views. `GraphController` adds the framework-\n// agnostic graph-source state machine (mode selection + load dispatch) on top\n// of @fortemi/core's repositories.\n//\n// Community *detection* intentionally lives in @fortemi/core (the base layer);\n// this package only renders/projects graphs it is given and orchestrates which\n// source to load.\n\nexport const VERSION = '2026.6.7'\n\nexport type {\n GraphNode,\n GraphEdge,\n GraphCommunity,\n CommunityGraph,\n GraphLayoutAlgorithm,\n PositionedGraphNode,\n PositionedGraph,\n GraphBounds,\n ViewportTransform,\n} from './types.js'\n\nexport { computeDegrees, nodeRadius } from './degree.js'\nexport type { NodeRadiusOptions } from './degree.js'\n\nexport { COMMUNITY_COLORS, UNASSIGNED_COMMUNITY_COLOR, colorForCommunity } from './color.js'\n\nexport { filterCommunityGraph } from './filter.js'\nexport type { GraphFilter } from './filter.js'\n\nexport { layoutCommunityGraph } from './layout.js'\nexport type { LayoutOptions } from './layout.js'\n\nexport { computeGraphBounds, fitGraphToViewport } from './bounds.js'\nexport type { FitOptions } from './bounds.js'\n\nexport {\n buildAdjacency,\n neighborsOf,\n expandNeighborhood,\n subgraphForNodes,\n neighborhoodSubgraph,\n} from './neighborhood.js'\nexport type { ExpandOptions } from './neighborhood.js'\n\nexport {\n GRAPH_SNAPSHOT_VERSION,\n serializeGraphSnapshot,\n stringifyGraphSnapshot,\n deserializeGraphSnapshot,\n} from './serialize.js'\nexport type { GraphSnapshot, SerializeSnapshotOptions } from './serialize.js'\n\nexport { GraphController } from './controller.js'\nexport type {\n GraphControllerDb,\n GraphSourceMode,\n GraphLayoutState,\n GraphTransitionState,\n GraphControllerStatus,\n GraphSourceRef,\n GraphControllerState,\n GraphControllerOptions,\n GraphControllerListener,\n} from './controller.js'\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fortemi/graph",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.7",
|
|
4
4
|
"description": "Framework-agnostic graph tooling for fortemi: pure TypeScript helpers for community-graph layout, filtering, color assignment, degree sizing, bounds/fit, neighborhood expansion, and static snapshot serialization, plus a framework-agnostic GraphController for graph-source selection. No React. Depends on @fortemi/core; consumed by @fortemi/react and JS-only hosts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fortemi",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"LICENSE"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@fortemi/core": "2026.6.
|
|
46
|
+
"@fortemi/core": "2026.6.7"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"tsup": "^8.3.5",
|