graph_kit 0.7.4 copy "graph_kit: ^0.7.4" to clipboard
graph_kit: ^0.7.4 copied to clipboard

A lightweight, in-memory graph library with pattern-based queries and efficient traversal for Dart and Flutter applications.

Graph Kit Logo

graph kit — lightweight typed directed multigraph + pattern queries

In-memory, typed directed multigraph with powerful and performant Cypher-inspired pattern queries

Pub License: MIT Dart Version Platform Support Open Issues Pull Requests Contributors Last Commit


In-memory, typed directed multigraph with:

  • Typed nodes (e.g., Person, Team, Project, Resource)
  • Typed edges (e.g., WORKS_FOR, MANAGES, ASSIGNED_TO, DEPENDS_ON)
  • Multiple relationships between the same nodes
  • Advanced Cypher queries with WHERE clauses, RETURN projection, logical operators, and variable-length paths
  • Complete path results with Neo4j-style edge information
  • Graph algorithms for analysis (shortest path, connected components, topological sort, reachability)

Table of Contents #

1. Quick Preview #

Graph Kit demo preview
Interactive graph algorithms demo showing centrality analysis in the Flutter app.

2. Complete Usage Examples #

This section provides copy-paste ready examples demonstrating all major query methods with a sample graph. Each example can be run as a standalone Dart script.

2.1 Setup: Sample Graph #

import 'package:graph_kit/graph_kit.dart';

void main() {
  // Create graph and add sample data
  final graph = Graph<Node>();

  // Add people
  graph.addNode(Node(id: 'alice', type: 'Person', label: 'Alice Cooper'));
  graph.addNode(Node(id: 'bob', type: 'Person', label: 'Bob Wilson'));
  graph.addNode(Node(id: 'charlie', type: 'Person', label: 'Charlie Davis'));

  // Add teams
  graph.addNode(Node(id: 'engineering', type: 'Team', label: 'Engineering'));
  graph.addNode(Node(id: 'design', type: 'Team', label: 'Design Team'));
  graph.addNode(Node(id: 'marketing', type: 'Team', label: 'Marketing'));

  // Add projects
  graph.addNode(Node(id: 'web_app', type: 'Project', label: 'Web Application'));
  graph.addNode(Node(id: 'mobile_app', type: 'Project', label: 'Mobile App'));
  graph.addNode(Node(id: 'campaign', type: 'Project', label: 'Ad Campaign'));

  // Add relationships
  graph.addEdge('alice', 'WORKS_FOR', 'engineering');
  graph.addEdge('bob', 'WORKS_FOR', 'engineering');
  graph.addEdge('charlie', 'MANAGES', 'engineering');
  graph.addEdge('charlie', 'MANAGES', 'design');
  graph.addEdge('charlie', 'MANAGES', 'marketing');
  graph.addEdge('engineering', 'ASSIGNED_TO', 'web_app');
  graph.addEdge('engineering', 'ASSIGNED_TO', 'mobile_app');
  graph.addEdge('design', 'ASSIGNED_TO', 'mobile_app');
  graph.addEdge('marketing', 'ASSIGNED_TO', 'campaign');
  graph.addEdge('alice', 'LEADS', 'web_app');

  final query = PatternQuery(graph);

  // Run examples below...
}

2.2 Basic Queries - Get Single Type #

// Get all people, query.match returns Map<String, Set<String>>
final people = query.match('person:Person');
print(people); // {person: {alice, bob, charlie}}

// Get all teams, query.match returns Map<String, Set<String>>
final teams = query.match('team:Team');
print(teams); // {team: {engineering, design, marketing}}

// Get all projects, query.match returns Map<String, Set<String>>
final projects = query.match('project:Project');
print(projects); // {project: {web_app, mobile_app, campaign}}

2.3 Relationship Queries - Get Connected Nodes #

// Find who works for teams, query.match returns Map<String, Set<String>>
final workers = query.match('person:Person-[:WORKS_FOR]->team:Team');
print(workers); // {person: {alice, bob}, team: {engineering}}

// Find who manages teams, query.match returns Map<String, Set<String>>
final managers = query.match('person:Person-[:MANAGES]->team:Team');
print(managers); // {person: {charlie}, team: {engineering, design, marketing}}

// Find team assignments to projects, query.match returns Map<String, Set<String>>
final assignments = query.match('team:Team-[:ASSIGNED_TO]->project:Project');
print(assignments); // {team: {engineering, design, marketing}, project: {web_app, mobile_app, campaign}}

2.4 Queries from Specific Starting Points #

// What does Alice work on? query.match with startId returns Map<String, Set<String>>
final aliceWork = query.match(
  'person-[:WORKS_FOR]->team-[:ASSIGNED_TO]->project',
  startId: 'alice'
);
print(aliceWork); // {person: {alice}, team: {engineering}, project: {web_app, mobile_app}}

// What does Charlie manage? query.match with startId returns Map<String, Set<String>>
final charlieManages = query.match(
  'person-[:MANAGES]->team',
  startId: 'charlie'
);
print(charlieManages); // {person: {charlie}, team: {engineering, design, marketing}}

// Who works on the web app project? query.match with startId returns Map<String, Set<String>>
final webAppTeam = query.match(
  'project<-[:ASSIGNED_TO]-team<-[:WORKS_FOR]-person',
  startId: 'web_app'
);
print(webAppTeam); // {project: {web_app}, team: {engineering}, person: {alice, bob}}

2.5 Row-wise Results - Preserve Path Relationships #

// Get specific person-team-project combinations, query.matchRows returns List<Map<String, String>>
final rows = query.matchRows('person-[:WORKS_FOR]->team-[:ASSIGNED_TO]->project');
print(rows);
// [
//   {person: alice, team: engineering, project: web_app},
//   {person: alice, team: engineering, project: mobile_app},
//   {person: bob, team: engineering, project: web_app},
//   {person: bob, team: engineering, project: mobile_app}
// ]

// Access individual path data
print(rows.first); // {person: alice, team: engineering, project: web_app}
print(rows.first['person']); // alice
print(rows.first['team']); // engineering

2.6 Complete Path Results with Edge Information #

// Get complete path information, query.matchPaths returns List<PathMatch>
final paths = query.matchPaths('person-[:WORKS_FOR]->team-[:ASSIGNED_TO]->project');
print(paths.length); // 4

// Print all paths with edges
for (final path in paths) {
  print(path.nodes); // {person: alice, team: engineering, project: web_app}
  for (final edge in path.edges) {
    print('  ${edge.from} -[:${edge.type}]-> ${edge.to}'); // alice -[:WORKS_FOR]-> engineering
  }
}

2.7 Multiple Pattern Queries #

// Get all of Alice's connections, query.matchMany returns Map<String, Set<String>>
final aliceConnections = query.matchMany([
  'person-[:WORKS_FOR]->team',
  'person-[:LEADS]->project'
], startId: 'alice');
print(aliceConnections); // {person: {alice}, team: {engineering}, project: {web_app}}

// Combine multiple relationship types, query.matchMany returns Map<String, Set<String>>
final allConnections = query.matchMany([
  'person:Person-[:WORKS_FOR]->team:Team',
  'person:Person-[:MANAGES]->team:Team',
  'person:Person-[:LEADS]->project:Project'
]);
print(allConnections); // {person: {alice, bob, charlie}, team: {engineering, design, marketing}, project: {web_app}}

2.8 WHERE Clause Filtering #

// Add people with properties for filtering examples
graph.addNode(Node(
  id: 'alice',
  type: 'Person',
  label: 'Alice Cooper',
  properties: {'age': 28, 'department': 'Engineering', 'salary': 85000}
));
graph.addNode(Node(
  id: 'bob',
  type: 'Person',
  label: 'Bob Wilson',
  properties: {'age': 35, 'department': 'Engineering', 'salary': 95000}
));

// Filter by age - query.matchRows returns List<Map<String, String>>
final seniors = query.matchRows('MATCH person:Person WHERE person.age > 30');
print(seniors); // [{person: bob}]

// Filter by department - query.matchRows returns List<Map<String, String>>
final engineers = query.matchRows('MATCH person:Person WHERE person.department = "Engineering"');
print(engineers); // [{person: alice}, {person: bob}]

// Combine conditions with AND - query.matchRows returns List<Map<String, String>>
final seniorEngineers = query.matchRows('MATCH person:Person WHERE person.age > 30 AND person.department = "Engineering"');
print(seniorEngineers); // [{person: bob}]

// Use OR conditions - query.matchRows returns List<Map<String, String>>
final youngOrWellPaid = query.matchRows('MATCH person:Person WHERE person.age < 30 OR person.salary > 90000');
print(youngOrWellPaid); // [{person: alice}, {person: bob}]

// Complex filtering with relationships - query.matchRows returns List<Map<String, String>>
final seniorWorkers = query.matchRows('MATCH person:Person-[:WORKS_FOR]->team:Team WHERE person.age > 30');
print(seniorWorkers); // [{person: bob, team: engineering}]

2.9 RETURN Clause - Property Projection #

The RETURN clause lets you project specific variables and properties, creating clean, production-ready result sets instead of raw node IDs.

Why use RETURN?

  • Clean data: Get only what you need (no extra lookups)
  • Custom names: Use AS aliases for readable column names
  • Type-safe patterns: Combine with Dart 3 destructuring
  • Performance: Reduces data transfer and processing

Basic RETURN - Variable Projection

// Without RETURN: Get node IDs (requires separate lookups)
final rawResults = query.matchRows('MATCH person:Person-[:WORKS_FOR]->team:Team');
print(rawResults); // [{person: alice, team: engineering}, ...]

// With RETURN: Get only what you need
final results = query.matchRows('MATCH person:Person-[:WORKS_FOR]->team:Team RETURN person, team');
print(results); // [{person: alice, team: engineering}, ...]
// Same format, but explicitly controlled

Property Access - Get Actual Data

// RETURN properties directly from nodes
final employeeData = query.matchRows(
  'MATCH person:Person-[:WORKS_FOR]->team:Team RETURN person.name, person.salary, team.name'
);
print(employeeData);
// [
//   {'person.name': 'Alice Cooper', 'person.salary': 85000, 'team.name': 'Engineering'},
//   {'person.name': 'Bob Wilson', 'person.salary': 95000, 'team.name': 'Engineering'}
// ]

// Access values
print(employeeData.first['person.name']); // Alice Cooper
print(employeeData.first['person.salary']); // 85000

AS Aliases - Custom Column Names

// Use AS to create readable column names
final cleanResults = query.matchRows(
  'MATCH person:Person-[:WORKS_FOR]->team:Team '
  'RETURN person.name AS employee, person.salary AS pay, team.name AS department'
);
print(cleanResults);
// [
//   {employee: 'Alice Cooper', pay: 85000, department: 'Engineering'},
//   {employee: 'Bob Wilson', pay: 95000, department: 'Engineering'}
// ]

// Much cleaner access!
print(cleanResults.first['employee']); // Alice Cooper
print(cleanResults.first['department']); // Engineering

Destructuring - Type-Safe Access (Dart 3)

Use Dart's pattern matching to destructure results type-safely:

// Destructure in a loop
final results = query.matchRows(
  'MATCH person:Person WHERE person.salary > 90000 '
  'RETURN person.name AS name, person.salary AS salary, person.department AS dept'
);

for (var {'name': employeeName, 'salary': pay, 'dept': department} in results) {
  print('$employeeName earns \$$pay in $department');
}
// Output:
// Bob Wilson earns $95000 in Engineering

Shorter syntax for single result:

final topEarner = query.matchRows(
  'MATCH person:Person RETURN person.name AS name, person.salary AS salary'
).first;

var {'name': name, 'salary': salary} = topEarner;
print('$name: \$$salary'); // Uses destructured variables directly

Combining WHERE + RETURN

// Filter with WHERE, then project with RETURN
final seniorEngineers = query.matchRows(
  'MATCH person:Person-[:WORKS_FOR]->team:Team '
  'WHERE person.age > 30 AND team.name = "Engineering" '
  'RETURN person.name AS engineer, person.age AS yearsOld, team.name AS teamName'
);

for (var {'engineer': name, 'yearsOld': age} in seniorEngineers) {
  print('$name ($age years old)');
}

Real-World Example - Employee Directory

// Complete employee report query
final report = query.matchRows(
  'MATCH person:Person-[:WORKS_FOR]->team:Team-[:WORKS_ON]->project:Project '
  'WHERE person.salary >= 85000 '
  'RETURN person.name AS employee, '
  '       person.role AS title, '
  '       person.salary AS compensation, '
  '       team.name AS department, '
  '       project.name AS currentProject'
);

// Use destructuring for clean output
for (var {
  'employee': name,
  'title': role,
  'compensation': salary,
  'department': dept,
  'currentProject': project
} in report) {
  print('$name ($role) - $dept - \$$salary - Working on: $project');
}
// Output:
// Alice Cooper (Senior Engineer) - Engineering - $85000 - Working on: Web Application
// Bob Wilson (Staff Engineer) - Engineering - $95000 - Working on: Mobile App

RETURN vs Raw IDs - Quick Comparison

Approach Result Format Use Case
No RETURN Node IDs only When you need to hydrate objects elsewhere
RETURN variables {person: 'alice', team: 'engineering'} ID-based hydration pattern
RETURN properties {'person.name': 'Alice', 'team.name': 'Engineering'} Direct property access
RETURN with AS {employee: 'Alice', dept: 'Engineering'} Clean, production-ready data

Try the interactive demo:

cd example
flutter run
# Select "RETURN Clause Projection" to see live examples

2.10 Utility Methods #

// Find by type, query.findByType returns Set<String>
final allPeople = query.findByType('Person');
print(allPeople); // {alice, bob, charlie}

// Find by exact label, query.findByLabelEquals returns Set<String>
final aliceIds = query.findByLabelEquals('Alice Cooper');
print(aliceIds); // {alice}

// Find by label substring, query.findByLabelContains returns Set<String>
final bobUsers = query.findByLabelContains('bob');
print(bobUsers); // {bob}

// Direct edge traversal, query.outFrom returns Set<String>
final aliceTeams = query.outFrom('alice', 'WORKS_FOR');
print(aliceTeams); // {engineering}

// Reverse edge traversal, query.inTo returns Set<String>
final engineeringWorkers = query.inTo('engineering', 'WORKS_FOR');
print(engineeringWorkers); // {alice, bob}

2.11 Summary of Query Methods #

Method Returns Use Case
match() Map<String, Set<String>> Get grouped node IDs by variable
matchMany() Map<String, Set<String>> Combine multiple patterns
matchRows() List<Map<String, String>> Preserve path relationships
matchPaths() List<PathMatch> Complete path + edge information
findByType() Set<String> All nodes of specific type
findByLabelEquals() Set<String> Nodes by exact label match
findByLabelContains() Set<String> Nodes by label substring
outFrom()/inTo() Set<String> Direct edge traversal

3. Graph Algorithms #

Graph Kit includes efficient implementations of common graph algorithms for analysis and pathfinding:

Available Algorithms #

  • Shortest Path - Find optimal routes between nodes using BFS (counts hops)
  • Path Enumeration - Find all possible routes between nodes within hop limits
  • Connected Components - Identify groups of interconnected nodes
  • Reachability Analysis - Discover all nodes reachable from a starting point
  • Topological Sort - Order nodes by dependencies (useful for build systems, task scheduling)
  • Centrality Analysis - Identify important nodes (betweenness and closeness centrality)

Shortest path algorithm visualization
Shortest path visualization highlighting the optimal route between selected nodes.

Quick Example #

import 'package:graph_kit/graph_kit.dart';

// Create a dependency graph
final graph = Graph<Node>();
graph.addNode(Node(id: 'core', type: 'Package', label: 'Core'));
graph.addNode(Node(id: 'utils', type: 'Package', label: 'Utils'));
graph.addNode(Node(id: 'app', type: 'Package', label: 'App'));

// Add dependencies (app depends on utils, utils depends on core)
graph.addEdge('utils', 'DEPENDS_ON', 'core');
graph.addEdge('app', 'DEPENDS_ON', 'utils');

// Use graph algorithms
final algorithms = GraphAlgorithms(graph);

// Find shortest path (counts hops)
final path = algorithms.shortestPath('app', 'core');
print('Path: ${path.path}'); // [app, utils, core]
print('Distance: ${path.distance}'); // 2

// Find all possible paths
final allPaths = enumeratePaths(graph, 'app', 'core', maxHops: 4);
print('Routes: ${allPaths.paths.length}'); // All alternative routes

// Get build order (topological sort)
final buildOrder = algorithms.topologicalSort();
print('Build order: $buildOrder'); // [core, utils, app]

// Find connected components
final components = algorithms.connectedComponents();
print('Components: $components'); // [{core, utils, app}]

// Check reachability
final reachable = algorithms.reachableFrom('app');
print('App can reach: $reachable'); // {app, utils, core}

// Check what can reach a target
final reachableBy = algorithms.reachableBy('core');
print('Can reach core: $reachableBy'); // {app, utils, core}

// Check all connected nodes (bidirectional)
final reachableAll = algorithms.reachableAll('utils');
print('Connected to utils: $reachableAll'); // {app, utils, core}

// Find critical bridge nodes
final betweenness = algorithms.betweennessCentrality();
print('Bridge nodes: ${betweenness.entries.where((e) => e.value > 0.3).map((e) => e.key)}');

// Find communication hubs
final closeness = algorithms.closenessCentrality();
print('Most central: ${closeness.entries.reduce((a, b) => a.value > b.value ? a : b).key}');

Visual Demo #

Run the interactive Flutter demo to see graph algorithms in action:

cd example
flutter run
  • Switch to "Graph Algorithms" tab
  • Click nodes to see shortest paths
  • View connected components with color coding
  • See topological sort with dependency levels
  • Explore reachability analysis

Command Line Demo #

For a focused algorithms demonstration:

dart run bin/algorithms_demo.dart

This shows practical examples of:

  • Package dependency analysis
  • Build order optimization
  • Component isolation detection
  • Shortest path finding

4. Generic Traversal Utilities #

For BFS-style subgraph exploration around nodes within hop limits. Unlike pattern queries that follow specific paths, this explores neighborhoods in all directions using specified edge types.

// Using the same graph setup from section 2...
// Add this to your existing code:

// Explore everything within 2 hops from Alice, expandSubgraph returns SubgraphResult
final aliceSubgraph = expandSubgraph(
  graph,
  seeds: {'alice'},
  edgeTypesRightward: {'WORKS_FOR', 'MANAGES', 'ASSIGNED_TO', 'LEADS'},
  forwardHops: 2,
  backwardHops: 0,
);
print(aliceSubgraph.nodes); // {alice, engineering, web_app, mobile_app}
print(aliceSubgraph.edges.length); // 4

// Explore everything connected to engineering team, expandSubgraph returns SubgraphResult
final engineeringSubgraph = expandSubgraph(
  graph,
  seeds: {'engineering'},
  edgeTypesRightward: {'ASSIGNED_TO'},
  edgeTypesLeftward: {'WORKS_FOR', 'MANAGES'},
  forwardHops: 1,
  backwardHops: 1,
);
print(engineeringSubgraph.nodes); // {engineering, web_app, mobile_app, alice, bob, charlie}
print(engineeringSubgraph.edges.length); // 5

// Find everyone within 2 hops of projects, expandSubgraph returns SubgraphResult
final projectEcosystem = expandSubgraph(
  graph,
  seeds: {'web_app', 'mobile_app', 'campaign'},
  edgeTypesRightward: {'WORKS_FOR', 'MANAGES'},
  edgeTypesLeftward: {'ASSIGNED_TO', 'LEADS'},
  forwardHops: 0,
  backwardHops: 2,
);
print(projectEcosystem.nodes); // {web_app, mobile_app, campaign, engineering, alice, design, marketing}
print(projectEcosystem.edges.length); // 7

When to use vs Pattern Queries #

Use Case Pattern Queries expandSubgraph
Specific paths person-[:WORKS_FOR]->team-[:ASSIGNED_TO]->project No
Neighborhood exploration No Yes - "Everything around Alice"
Impact analysis No Yes - "What's affected by this change?"
Subgraph extraction No Yes - For visualization/analysis
Known relationships Yes - Clear path patterns No
Unknown structure No Yes - Explore what's connected

5. Pattern Query Examples #

  • Simple patterns: "user:User"{alice, bob, charlie}
  • Forward patterns: "user-[:MEMBER_OF]->group"
  • Backward patterns: "resource<-[:CAN_ACCESS]-group<-[:MEMBER_OF]-user"{alice, bob}
  • Label filtering: "user:User{label~Admin}"{bob}
  • Multiple edge types: "person-[:WORKS_FOR|VOLUNTEERS_AT]->org" → matches ANY of the specified relationship types
  • Mixed directions: "person1-[:WORKS_FOR]->team<-[:MANAGES]-manager" → finds common connections and shared relationships
  • Variable-length paths: "manager-[:MANAGES*1..3]->subordinate" → finds direct and indirect reports

6. Mini-Cypher Reference #

GraphKit uses a simplified version of Cypher - the query language used by Neo4j (the most popular graph database). Think of it like SQL for graphs.

What is Cypher? #

Cypher is a language designed to describe patterns in graphs. Instead of writing complex code to traverse relationships, you draw the path with text:

  • SQL: SELECT * FROM users WHERE department = 'engineering'
  • Cypher: person:Person-[:WORKS_FOR]->team:Team{label=Engineering}

What is "Mini-Cypher"? #

GraphKit supports a subset of Cypher - the most useful parts without the complexity:

Supported: Basic patterns, node types, relationships, label filters, variable-length paths, WHERE clauses with logical operators, parentheses Not supported: Aggregations, complex subqueries

This gives you the power of graph queries without learning the full Cypher language.

How to Build Pattern Queries #

Think of pattern queries like giving directions. Instead of "turn left at the store", you're saying "follow this relationship to that type of thing".

Step 1: Start with What You Want to Find #

When you want to find all people in your company, you write:

query.match('person:Person')

This breaks down into:

  • person = What you want to call them in your results (like a nickname)
  • : = "that are of type"
  • Person = The actual type of thing you're looking for

Think of it like: "Find me all things of type Person, and I'll call them 'person' in my results"

Step 2: Connect Things with Arrows #

Now say you want to know "who works where". You connect person to team:

query.match('person:Person-[:WORKS_FOR]->team:Team')

Reading left to right:

  • person:Person = "Start with a person"
  • -[ = "who has a connection"
  • :WORKS_FOR = "of type WORKS_FOR"
  • ]-> = "that points to"
  • team:Team = "a team"

Like saying: "Show me people who have a WORKS_FOR arrow pointing to teams"

Step 3: The Arrow Direction Matters! #

Right arrow -> means "going out from":

person-[:WORKS_FOR]->team    // Person points to team (person works FOR the team)

Left arrow <- means "coming in to":

team<-[:WORKS_FOR]-person    // Person points to team (team is worked for BY person)

Same relationship, different starting point!

Step 4: Chain Multiple Steps #

Want to follow a longer path? Just keep adding arrows:

person-[:WORKS_FOR]->team-[:ASSIGNED_TO]->project

This means:

  1. Start with a person
  2. Follow their WORKS_FOR connection to a team
  3. Follow that team's ASSIGNED_TO connection to a project

Like following a trail: person → team → project

Step 5: Filter by Name #

Want to find a specific person? Add their name in curly braces:

person:Person{label=Alice Cooper}     // Find exactly "Alice Cooper"
person:Person{label~alice}            // Find anyone with "alice" in their name

The ~ means "contains" (like a fuzzy search)

Quick Examples to Try #

// Simple: Find all people
query.match('person:Person')
// Returns: {person: {alice, bob, charlie}}

// Connection: Who works where?
query.match('person-[:WORKS_FOR]->team')
// Returns: {person: {alice, bob}, team: {engineering}}

// Chain: Follow a path through the graph
query.match('person-[:WORKS_FOR]->team-[:ASSIGNED_TO]->project')
// Returns: {person: {alice, bob}, team: {engineering}, project: {web_app, mobile_app}}

// Backwards: What teams work on this project?
query.match('project<-[:ASSIGNED_TO]-team', startId: 'web_app')
// Returns: {project: {web_app}, team: {engineering}}

// Filter: Find specific person
query.match('person:Person{label~Alice}')
// Returns: {person: {alice}}

Remember: The names you pick (person, team, etc.) become the keys in your results!

Variable-Length Paths #

Variable-length paths let you find connections across multiple hops without specifying the exact number of steps:

// Use the unified PatternQuery (includes all advanced features)
final query = PatternQuery(graph);

// Find all direct and indirect reports (1-3 management levels)
query.match('manager-[:MANAGES*1..3]->subordinate')

// Find anyone at least 2 levels down the hierarchy
query.match('manager-[:MANAGES*2..]->subordinate')

// Find dependencies up to 4 steps away
query.match('component-[:DEPENDS_ON*..4]->dependency')

// Find all reachable dependencies (unlimited hops)
query.match('component-[:DEPENDS_ON*]->dependency')

Variable-length syntax:

  • [:TYPE*] - Unlimited hops
  • [:TYPE*1..3] - Between 1 and 3 hops
  • [:TYPE*2..] - 2 or more hops
  • [:TYPE*..4] - Up to 4 hops
  • [:TYPE*2] - Exactly 2 hops

Note: Variable-length paths are fully supported in the unified PatternQuery implementation.

6.1 Advanced WHERE Clauses and Complex Filtering #

For sophisticated filtering beyond basic patterns, GraphKit supports full WHERE clause syntax with logical operators and parentheses.

Complete Cypher Query Language Guide

The comprehensive guide covers:

  • Complex logical expressions with parentheses: (A AND B) OR (C AND D)
  • Multiple comparison operators: >, <, >=, <=, =, !=
  • Real-world query examples for HR, project management, and organizational analysis
  • Property filtering best practices and performance tips
  • Error handling and troubleshooting

Quick examples:

// Complex filtering with parentheses
MATCH person:Person WHERE (person.age > 40 AND person.salary > 100000) OR person.department = "Management"

// Multi-hop with filtering
MATCH person:Person-[:WORKS_FOR]->team:Team-[:WORKS_ON]->project:Project WHERE person.salary > 80000 AND project.status = "active"

Try the Interactive WHERE Demo:

cd example
flutter run -t lib/where_demo.dart

The demo includes sample queries, real-time query execution, and a comprehensive dataset for testing complex WHERE clauses.

7. Comparison with Cypher #

Feature Real Cypher graph_kit
Mixed directions Yes Yes
Variable length paths Yes Yes
Multiple edge types [:TYPE1|TYPE2] Yes
Multiple patterns pattern1, pattern2 No
Optional matches Yes Via matchMany
WHERE clauses Yes Yes
Logical operators Yes Yes (AND, OR)
Parentheses Yes Yes

8. Design and performance #

  • Traversal from a known ID (startId) is fast:
    • Each hop uses adjacency maps; cost is proportional to the edges visited.
  • Seeding by type (alias:Type) does a one-time node scan to find initial seeds.
    • For small/medium graphs, this is effectively instant; indexing can be added later if needed.
  • matchMany([...]) mirrors "multiple MATCH/OPTIONAL MATCH" lines in Cypher by running several independent chains from the same start and unioning results.

9. JSON Serialization #

Save and load graphs to/from JSON for persistence and data exchange:

import 'dart:io';
import 'package:graph_kit/graph_kit.dart';

// Build your graph
final graph = Graph<Node>();
graph.addNode(Node(id: 'alice', type: 'User', label: 'Alice',
  properties: {'email': 'alice@example.com', 'active': true}));
graph.addNode(Node(id: 'team1', type: 'Team', label: 'Engineering'));
graph.addEdge('alice', 'MEMBER_OF', 'team1');

// Serialize to JSON
final json = graph.toJson();
final jsonString = graph.toJsonString(pretty: true);

// Save to file
await File('graph.json').writeAsString(jsonString);

// Load from file
final loadedJson = await File('graph.json').readAsString();
final restoredGraph = GraphSerializer.fromJsonString(loadedJson, Node.fromJson);

// Graph is fully restored - queries work immediately
final query = PatternQuery(restoredGraph);
final members = query.match('team<-[:MEMBER_OF]-user', startId: 'team1');
print(members['user']); // {alice}

10. Examples index #

Dart CLI Examples #

  • bin/showcase.dart – comprehensive graph demo with multiple query examples
  • bin/access_control.dart – access control patterns with users, groups, and resources
  • bin/project_dependencies.dart – project dependency analysis and traversal
  • bin/social_network.dart – social network relationships and friend recommendations
  • bin/serialization_demo.dart – JSON serialization and persistence
  • bin/algorithms_demo.dart – graph algorithms demonstration with dependency analysis

Run any example:

dart run bin/showcase.dart
dart run bin/access_control.dart

Flutter Example App #

Interactive graph visualization with pattern queries:

cd example

# Main demo - visual graph with pattern queries
flutter run

# WHERE clause demo - interactive Cypher query testing
flutter run -t lib/where_demo.dart

# RETURN clause demo - property projection and destructuring
flutter run -t lib/return_demo.dart

# Features:
# - Visual graph with nodes and edges
# - Live pattern query execution
# - WHERE clause testing with sample data
# - RETURN clause with before/after comparison
# - Destructuring examples
# - Path highlighting and visualization
# - Example queries with one-click execution

License #

See LICENSE.

2
likes
150
points
588
downloads

Publisher

verified publishercodealchemist.dev

Weekly Downloads

A lightweight, in-memory graph library with pattern-based queries and efficient traversal for Dart and Flutter applications.

Repository (GitHub)
View/report issues

Topics

#graph #graphs #directed #cypher #multigraph

Documentation

API reference

License

MIT (license)

Dependencies

petitparser

More

Packages that depend on graph_kit