Graphora API
    Preparing search index...

    Module @graphora/core

    Core Package

    Package name: @graphora/core

    Responsibility: graph data model types, raw graph validation, normalization, JSON interchange, business-record mapping, read selectors, snapshots, runtime collections, mutations, and graph events.

    import { createGraphStore } from '@graphora/core'

    const store = createGraphStore({
    nodes: [{ id: 'a' }, { id: 'b' }],
    edges: [{ source: 'a', target: 'b' }]
    })

    createGraphStore(graph) returns a GraphStore. You can also instantiate new GraphStore(graph) directly. A store created without graph data starts at version 0; a successfully loaded graph starts at version 1.

    • Node and explicit edge IDs may be strings or finite numbers.
    • Internal lookup keys use String(id), so 1 and "1" collide.
    • edges is optional and defaults to an empty array.
    • Edge directed defaults to true.
    • Edges without IDs receive deterministic generated IDs using the reserved __edge: prefix.
    • Self-loops and parallel edges are valid.
    • Invalid graph loading throws GraphError with collected GraphIssue entries.

    Optional fields are validated too: labels must be strings, edge directed values must be booleans, positions must contain finite coordinates, and attributes must follow the recursive GraphAttributeValue shape without cycles, sparse arrays, accessors, or custom prototypes.

    Core exposes a small JSON boundary around RawGraph:

    import {
    createGraphStore,
    parseRawGraphJson,
    serializeRawGraph,
    snapshotToRawGraph
    } from '@graphora/core'

    const saved = serializeRawGraph(snapshotToRawGraph(store.snapshot()), {
    space: 2
    })
    const reloaded = parseRawGraphJson(saved)
    const nextStore = createGraphStore(reloaded)

    parseRawGraphJson reports syntax and graph-shape failures as GraphError. snapshotToRawGraph preserves IDs, endpoints, labels, attributes, directed flags, finite positions, and opaque data references. It writes generated edge IDs as explicit raw IDs and omits node positions that are null.

    serializeRawGraph rejects opaque data that JSON would silently drop or coerce. This includes nested undefined, functions, symbols, bigints, non-finite numbers, cycles, sparse arrays, accessors, custom toJSON behavior, and class, Date, Map, Set, or array-subclass instances. Convert these values to plain JSON data before saving. Optional graph fields set to undefined are omitted. Pretty-print space accepts an integer from 0 through 10 or at most 10 JSON whitespace characters.

    createRawGraphFromRecords maps arrays or other iterables with explicit, stateless accessors:

    import { createRawGraphFromRecords } from '@graphora/core'

    const graph = createRawGraphFromRecords({
    nodes: services,
    edges: dependencies,
    node: {
    id: (service) => service.key,
    label: (service) => service.name,
    data: (service) => service
    },
    edge: {
    source: (dependency) => dependency.from,
    target: (dependency) => dependency.to,
    label: (dependency) => dependency.protocol,
    data: (dependency) => dependency
    }
    })

    The mapper returns new raw node and edge containers, retains values returned by data accessors by reference, never mutates source records, and validates the completed RawGraph. When edges is supplied, edge mapping is required. Omit both to create a node-only graph.

    The current implementation supports:

    • getNode(id)
    • getEdge(id)
    • getNodes()
    • getEdges()
    • getOutgoingEdges(nodeId)
    • getIncomingEdges(nodeId)
    • getIncidentEdges(nodeId)
    • getNeighbors(nodeId, direction?)
    • getEdgesBetween(sourceId, targetId, options?)
    • getBounds()
    • getVersion()
    • snapshot()

    Selector arrays are deterministic and read-only from the consumer perspective. Unknown IDs return undefined or empty arrays instead of throwing.

    The store supports strict graph mutations:

    • loadGraph(graph)
    • clear()
    • addNode(node)
    • addEdge(edge)
    • removeNode(id)
    • removeEdge(id)
    • updateNodeData(id, data)
    • updateEdgeData(id, data)
    • updateNodeAttributes(id, attributes, mode?)
    • updateEdgeAttributes(id, attributes, mode?)
    • updateNodePosition(id, position)

    Successful mutations return { ok: true, version } and increment the graph version exactly once. Invalid mutations throw GraphError; selectors stay forgiving.

    updateNodePositions(updates) is a narrow atomic operation for layout result application. It validates all IDs and finite points before mutation, rejects duplicate IDs, rebuilds state once, increments the version once, then emits one node:positions-updated summary and one graph:changed event with reason position:update. Empty input is a no-op. updateNodePosition keeps its existing one-node behavior and granular event.

    Removing a node also removes every connected edge by default, including incoming edges, outgoing edges, self-loops, and parallel edges. Remaining nodes and edges are reindexed in deterministic order, adjacency indices are rebuilt, and bounds are recomputed.

    applyBatch(operations, options?) applies ordered node and edge mutations to a private draft, then publishes one rebuilt snapshot. A nonempty successful batch increments the version once and returns its operation count plus deterministic, first-seen affected node and edge IDs. A node added earlier in a batch can be an endpoint for an edge added later. Removing a node also records and removes its incident draft edges.

    The nine operations cover node/edge addition and removal, node/edge data and attribute updates, and node position updates. Attribute merge/replace and null positions use the same rules as the one-item methods. options.id is an optional nonempty correlation label. Without one, a successful batch uses batch:<final-version>; this label is deterministic, not globally unique.

    Empty batches return a summary at the current version and emit no events. If any operation fails, the store, version, generated-edge sequence, and event stream remain unchanged. Use one-item mutations when observers need granular events, applyBatch for related incremental changes, and loadGraph to replace the whole graph.

    Use store.nodes(ids?) and store.edges(ids?) to capture existing members, or selectNodes(predicate) / selectEdges(predicate) to filter once. Unknown IDs are ignored and numeric/string aliases are deduplicated. Omitting IDs captures all current members; passing [] captures none.

    const downstream = store.nodes(['a']).neighbors('out')
    const labels = downstream.map((node) => node.label ?? String(node.id))
    const edges = downstream.incidentEdges().filter((edge) => edge.directed)
    const result = downstream.updateAttributes({ highlighted: true })

    Collections have size, has(id), ids(), keys(), toArray(), filter, map and forEach. Node collections also expose neighbors(direction?), incidentEdges(), outgoingEdges() and incomingEdges(). neighbors defaults to both; out follows source → target and in reverses it, including for directed: false, matching the existing store selectors. Self-loops do not make a node its own neighbor. Edge traversals retain loops and parallel edges, once per edge ID. Traversal and filter results capture their own membership.

    Membership is fixed by normalized key, while every read resolves current records in current store order. Removed members are skipped; re-adding a captured key restores its membership with the new stored ID representation. Other new keys do not join an old collection. loadGraph has the same key-based behavior. To reevaluate a predicate or adjacency after changes, construct a new result. Collections install no subscriptions and need no disposal.

    Returned arrays are frozen; opaque data values remain application-owned references. Read callbacks see one captured record array, even when they mutate the store independently. Read/filter methods scan current node or edge arrays; collections are convenience views, not an indexed query engine.

    updateData((record, index) => nextData) and updateAttributes(attributes, mode?) submit one atomic batch for active members. Attribute mode defaults to merge and also accepts replace. Empty collections are no-ops. A throwing data updater applies none of the helper's changes. If an updater mutates the store, the helper stops and throws GraphError with code INVALID_GRAPH; independent callback side effects remain. Updaters run synchronously and their returned values are stored as opaque data. Return new data rather than mutating existing application objects when rollback matters. Observer exceptions follow the same post-commit policy as applyBatch.

    The batch example uses engine.store.nodes(['api']).neighbors('out').updateAttributes(...) to recolor only the direct downstream node through the ordinary engine invalidation path.

    GraphStore implements a small synchronous typed event target:

    const unsubscribeNodeAdded = store.on('node:added', (event) => {
    console.log(event.nodeId, event.version)
    })

    const unsubscribeAll = store.onAny((event) => {
    console.log(event.type, event.version)
    })

    unsubscribeNodeAdded()
    unsubscribeAll()

    Supported event types:

    • graph:loaded
    • graph:cleared
    • node:added
    • node:removed
    • node:updated
    • edge:added
    • edge:removed
    • edge:updated
    • node:position-updated
    • node:positions-updated
    • graph:batch-applied
    • graph:changed

    Every successful mutation emits its granular event or events first, then one aggregate graph:changed event with the same final graph version. graph:changed.reason is one of load, clear, node:add, node:remove, node:update, edge:add, edge:remove, edge:update, or position:update.

    removeNode(id) emits one edge:removed event for each connected edge in current edge order, then node:removed, then one graph:changed. Removal events include IDs and normalized keys captured before removal. Add and update events include the current frozen runtime record where it still exists.

    Invalid mutations throw GraphError before state changes, version increments, or event emission. Unsubscribe functions are safe to call more than once, and events are dispatched synchronously without timers, promises, DOM EventTarget, or external emitter dependencies.

    A successful nonempty batch emits one graph:batch-applied summary followed by one graph:changed event with reason batch; it does not emit per-item events. Synchronous observer exceptions retain the existing event policy: the exception propagates after publication and may interrupt later listeners or the aggregate change event. Observer exceptions do not roll back committed graph state.

    This package currently implements raw graph normalization, JSON interchange, record mapping, read access, runtime collections, scoped graph mutations, general atomic batches, atomic multi-node position updates, snapshots, and graph event subscriptions. Renderer behavior, layouts, interactions, ports, handles, grouping, filtering, undo/redo, schema migration, remote fetch helpers, non-JSON format parsers, async event queues, DOM EventTarget integration, and GraphML are deferred to later tasks.

    GraphError
    GraphStore
    EdgeAddedEvent
    EdgeRemovedEvent
    EdgesBetweenOptions
    EdgeUpdatedEvent
    GraphBatchAppliedEvent
    GraphBatchMutationResult
    GraphBatchOptions
    GraphBounds
    GraphChangedEvent
    GraphClearedEvent
    GraphEdge
    GraphEdgeCollection
    GraphEventBase
    GraphEventTarget
    GraphIssue
    GraphLoadedEvent
    GraphMutationResult
    GraphNode
    GraphNodeCollection
    GraphSelectors
    GraphSnapshot
    LayoutAdjacency
    LayoutGraphSnapshot
    NodeAddedEvent
    NodePositionsUpdatedEvent
    NodePositionUpdate
    NodePositionUpdatedEvent
    NodeRemovedEvent
    NodeUpdatedEvent
    PackageInfo
    Point
    RawEdge
    RawEdgeRecordMapper
    RawGraph
    RawGraphRecordsInput
    RawNode
    RawNodeRecordMapper
    SerializeRawGraphOptions
    AttributeUpdateMode
    GraphAttributes
    GraphAttributeValue
    GraphBatchOperation
    GraphBatchReason
    GraphChangedReason
    GraphErrorCode
    GraphEvent
    GraphEventType
    GraphId
    InternalGraphId
    NeighborDirection
    RecordAccessor
    Unsubscribe
    corePackageInfo
    corePackageName
    createGraphStore
    createRawGraphFromRecords
    parseRawGraphJson
    serializeRawGraph
    snapshotToRawGraph
    validateRawGraph