Nodal.Tool 0.1.0-beta.14

This is a prerelease version of Nodal.Tool.
dotnet tool install --global Nodal.Tool --version 0.1.0-beta.14
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Nodal.Tool --version 0.1.0-beta.14
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Nodal.Tool&version=0.1.0-beta.14&prerelease
                    
nuke :add-package Nodal.Tool --version 0.1.0-beta.14
                    

NODAL FRAMEWORK IS OPEN SOURCE UNDER MPL-2.0.

Documentation is licensed under CC BY 4.0. Nodal names, logos, and trademarks are not granted under the software license. Hosted services, premium models, datasets, commercial APIs, and enterprise support are governed by separate commercial terms. Contributions require acceptance of the Nodal Contributor License Agreement.

Nodal Framework

Nodal Framework is a provider-based .NET graph data access prototype. It keeps the domain model and query API provider-neutral while Neo4j and TigerGraph packages compile and execute the same model through their native transports.

Packages

Package Purpose
Nodal.Core Provider-neutral model, LINQ query surface, tracking, and unit of work
Nodal.Migrations Portable graph schema migration contracts and planning
Nodal.Neo4j Neo4j/Cypher provider using the official pooled Bolt driver
Nodal.Analytics Provider-neutral analytics contracts and capability integration
Nodal.TigerGraph TigerGraph/GSQL provider using REST++ and an optional administrative transport
Nodal.Tool .NET global tool for deterministic migrations and evidence-driven CSV imports
Nodal.Import Provider-neutral, bounded import orchestration and diagnostics
Nodal.Import.Csv Streaming CSV records with deterministic header normalization
Nodal.Import.Relational SQL Server/PostgreSQL discovery, bounded reads, and deterministic relational interaction models

The beta line targets .NET 10. Package versions move together so provider and core contracts remain compatible during the pre-release period.

Development is specification-driven. Accepted feature contracts, provider capability decisions, and architecture records live in the versioned specification repository; CI rejects undocumented or unreferenced behavioral work before it reaches developer.

Install one provider package; it brings Nodal.Core transitively. Add the migration package only when the application owns schema evolution:

dotnet add package Nodal.Neo4j --prerelease
# or: dotnet add package Nodal.TigerGraph --prerelease
dotnet add package Nodal.Migrations --prerelease

Install the CLI separately as a .NET tool:

dotnet tool install --global Nodal.Tool --prerelease
nodal migrations validate --snapshot nodal.snapshot.json

The same tool validates a CSV import without connecting to a database and writes deterministic JSON evidence for review:

nodal import csv \
  --input world-food-delivery.csv \
  --mapping world-food-delivery.mapping.json \
  --evidence import-evidence.json

Relational inspection uses an application-owned composition host, so database credentials and provider clients never become CLI arguments. It writes the canonical interaction model and optional visualization projections in one metadata pass:

nodal import relational \
  --output northwind.nodalmodel.json \
  --graphml northwind.graphml \
  --gexf northwind.gexf \
  --dot northwind.dot

The trusted host is selected through NODAL_RELATIONAL_HOST_ASSEMBLY and NODAL_RELATIONAL_HOST_TYPE. It implements IRelationalInspectionHost and owns connection creation, pooling, authentication, secret retrieval, and the SQL Server or PostgreSQL adapter choice.

The repository also contains a structured World Food Delivery clean-room consumer. It restores only NuGet packages and exercises CSV-to-POCO mapping, graph mutation planning, portable Neo4j/TigerGraph queries, migrations, and a normalized relational interaction network. Its domain nodes and relations use one type per file so the example can serve as an application template rather than an opaque CI fixture.

Dry-run is the default. Applying a CSV requires both --apply true and, when property upserts or omissions are detected, --approve-destructive true plus a trusted provider-composed mutation host. The complete mapping format and apply boundary are documented in the import guide.

For one-time onboarding, Nodal.Import converts an explicitly mapped, bounded source batch into the same provider-neutral mutation plan accepted by Neo4j and TigerGraph. Planning performs no database I/O and returns reviewable dry-run evidence before a provider executes anything:

var mapping = GraphImportMapping.For<OrderRow>()
    .Node<Customer>("customer", "Customer", "Id", row => row.CustomerId,
        node => node.Property("Name", row => row.CustomerName))
    .Node<Order>("order", "Order", "Id", row => row.OrderId,
        node => node.Property("Total", row => row.Total))
    .Relation("placed", "customer", "order", "PLACED")
    .Build();

var planned = new GraphImportPlanner<OrderRow>().Plan(
    new GraphImportBatch<OrderRow>(1, rows),
    mapping,
    new GraphImportPlanningOptions(MaxOperations: 5_000));

if (!planned.DryRun.Succeeded || planned.DryRun.HasDestructiveRisks)
{
    Review(planned.DryRun);
}

Duplicate identities are coalesced deterministically within the batch, nodes always precede relations, and missing identities or potential property overwrites remain visible in payload-safe diagnostics and risk indicators. See the import guide for the complete contract.

Nodal.Import.Relational can also turn discovered tables, views, columns, primary keys, composite foreign keys, and referential actions into a canonical Relational Interaction Model. The JSON document preserves physical database evidence; GraphML, GEXF, and DOT exports provide readable visualization projections for tools such as Gephi. Suggested labels are structural hints marked for review, not inferred business semantics. Domain-level transformation into a knowledge graph remains an explicit application decision.

Immutable migration bundles capture provider identity, required capabilities, ordered up/down commands, execution channels, and destructive flags under a canonical SHA-256 checksum. NodalMigrationBundleExecutor provides idempotent, provider-neutral apply, rollback, dry-run, checksum-drift detection, explicit destructive approval, and optional exclusive provider locking. CLI apply and rollback load a trusted, provider-composed execution host named by environment variables; connection credentials remain inside that deployment host and never enter arguments, plans, bundles, or command output.

Nodal.Analytics is an optional public contract layer above providers. It keeps provider-executed analytics integration and capability declarations separate from the query and migration foundations. Advanced analytics implementations are not part of the open-source package contract.

Pin all packages to the same version for reproducible builds, for example 0.1.0-beta.1. The complete console, worker, and ASP.NET Core setup is in the installation guide.

Compatibility and provider capabilities

Nodal distinguishes vendor client compatibility from versions verified by this repository. The current live QA baselines are Neo4j 5.26 Community and TigerGraph 4.2.4 Community. Nodal.Neo4j uses Neo4j.Driver 6.3.0; the vendor states that driver 6.x connects to Neo4j 4.4.x, 5.x, 2025.x, and 2026.x, but those additional server families are not yet Nodal-certified. Neo4j 5.26 analytics require the vendor-matched GDS 2.13 release.

Capability Neo4j TigerGraph
Parameterized queries and fixed traversals Supported Supported
Variable-depth traversal Supported GSQL Syntax V2; live verified for bounded node traversal
Optional match Supported Not supported
Correlated WhereExists / WhereNotExists Supported Conditional: explicit runtime-generated extension plus administrative transport; live verified
Additional patterns and set operations Supported Not supported; rejected before transport
Scalar and aggregate rows Supported SQL-like GSQL Syntax V2; Count, Sum, Average, Min, and Max live verified
Transaction boundary Client-managed transaction Atomic request or installed query
Migration execution Supported Requires administrative transport
Centrality and community detection Requires compatible GDS and named projection Requires explicitly configured installed GSQL query
Weighted analytics Algorithm-specific Must be declared for each installed query
Typed shortest paths Native Cypher; GDS for weighted algorithms Configured installed GSQL query

Analytics families currently represented by the portable contract:

Family Algorithms Neo4j status TigerGraph status
Centrality ArticleRank, articulation points, betweenness, bridges, CELF, closeness, degree, eigenvector, harmonic, HITS, PageRank GDS compiler; live certification pending Installed-query contract; configured per deployment
Community Clique counting, conductance, HDBSCAN, K-core, K-1 coloring, K-means, label propagation, Leiden, local clustering coefficient, Louvain, modularity, modularity optimization, SCC, triangle count, WCC, maximum k-cut, SLLPA GDS compiler; live certification pending Installed-query contract; configured per deployment
Path finding Shortest/all-shortest, Dijkstra, A*, Yen Native unweighted execution and GDS weighted compiler Installed-query execution with canonical routes

The full matrix and verification legend are published in the compatibility documentation. Unsupported capabilities fail before transport and are never emulated by downloading the graph into application memory.

Weekly Dependabot checks cover NuGet, documentation npm packages, Docker database images, and GitHub Actions. Update pull requests target developer; a database version becomes a supported Nodal baseline only after its compatibility and live integration suites pass.

Attribute-based model

Attributes describe only portable graph semantics. Database-specific indexes, constraints, storage options, and migration details remain in the fluent migration API so domain POCOs do not become coupled to one graph database.

using Nodal.Core.Metadata;

[GraphNode("Person")]
public sealed record Person(
    [property: GraphKey]
    [property: GraphProperty("person_id")]
    string Id,

    [property: GraphProperty("display_name")]
    string Name)
{
    [GraphIgnore]
    public string DisplayLabel => $"{Name} ({Id})";
}

[GraphRelation("KNOWS", Directed = true)]
public sealed class Knows(DateTime since)
{
    [GraphProperty("since_at")]
    public DateTime Since { get; set; } = since;
}
  • [GraphNode] maps a CLR node type to its provider-neutral label or vertex type.
  • [GraphRelation] maps an edge POCO and records whether its direction is meaningful.
  • [GraphKey] selects the stable domain identifier. By convention, Id and <TypeName>Id also work.
  • [GraphProperty] maps both node and relationship payload properties.
  • [GraphIgnore] excludes calculated, transient, or application-only properties.

Fluent model configuration has the highest precedence, attributes come next, and conventions are the fallback.

Context and strongly typed sets

Public GraphSet<T> and RelationSet<TSource, TRelation, TTarget> properties are discovered automatically:

public sealed class SocialGraphContext(IGraphProvider provider) : NodalContext(provider)
{
    public GraphSet<Person> People => Set<Person>();

    public RelationSet<Person, Knows, Person> Friendships =>
        Relations<Person, Knows, Person>();
}

The same LINQ expression is translated using the mapped graph property names:

var adults = await context.People
    .Match(person => person.Name == "Ada" && person.Id == "person-42")
    .Take(10)
    .ToListAsync();

Relationships can be traversed without introducing provider-specific query text. Where after a traversal filters the reached node, and the result is materialized as that node's POCO type:

var peopleKnownByAda = await context.People
    .Match(person => person.Id == "person-42")
    .Traverse(context.Friendships)
    .Where(person => person.Name != "Grace")
    .Take(10)
    .ToListAsync();

var peopleWhoKnowAda = await context.People
    .Match(person => person.Id == "person-42")
    .TraverseIncoming(context.Friendships)
    .ToListAsync();

The same provider-neutral traversal model compiles to directed Cypher patterns for Neo4j and directed GSQL path patterns for TigerGraph. Relations declared with Directed = false automatically use an undirected traversal.

Query engine

The fluent query surface keeps values parameterized while pushing filtering, ordering, paging, traversal, duplicate elimination, and provider-supported projections into the selected provider. Requested operations that a provider has not verified fail before transport:

string[] selectedIds = ["person-42", "person-84"];

var page = await context.People.Query()
    .Where(person => selectedIds.Contains(person.Id))
    .Where(person => person.Name.StartsWith("Ad") && person.Name != null)
    .OrderBy(person => person.Name)
    .ThenByDescending(person => person.Id)
    .Skip(20)
    .Take(10)
    .Distinct()
    .AsNoTracking()
    .Select(person => new { person.Id, person.Name })
    .ToListAsync();

var exists = await context.People.Match(person => person.Name.Contains("Lovelace")).AnyAsync();
var count = await context.People.Match(person => selectedIds.Contains(person.Id)).CountAsync();
var person = await context.People.Match(person => person.Id == "person-42").SingleAsync();

FirstAsync, FirstOrDefaultAsync, SingleAsync, SingleOrDefaultAsync, AnyAsync, and CountAsync apply bounded or aggregate execution. CountAsync uses a server-side aggregate when paging has not changed LINQ count semantics. AsAsyncEnumerable provides cancellation-aware asynchronous consumption; HTTP providers necessarily receive one response payload, while the API keeps consumer code provider-neutral.

Neo4j additionally supports correlated existence checks, independently named required patterns, provider-side row aggregates, and compatible node-query unions. Each value remains parameterized; the second union operand is automatically rebased so parameter names cannot collide:

var selected = await context.People.Match(person => person.Active)
    .Union(context.People.Match(person => person.Name.StartsWith("Ada")))
    .OrderBy(person => person.Name)
    .Take(50)
    .ToListAsync();

var summary = await context.People.Query()
    .ToRows()
    .Select("name", person => person.Name)
    .Count("people")
    .Having("people", GraphComparisonOperator.GreaterThan, 1)
    .OrderByDescending("people")
    .ToListAsync();

Scalar columns selected together with aggregate columns define the provider-side grouping key. TigerGraph compiles these shapes through SQL-like GSQL Syntax V2 and normalizes its tabular response. Optional traversal, additional named patterns, and set operations remain explicitly unavailable on TigerGraph and fail before transport rather than being simulated in memory. Correlated existence is available only when the host opts into Nodal-generated queries and supplies an administrative transport; the provider then creates or replaces, installs, and executes a deterministic query through REST++.

Graph analytics retain the same typed model while executing centrality and community algorithms on the provider:

var influentialPeople = await context.People.Query()
    .Analyze(context.Friendships)
    .PageRank()
    .OnProjection("social")
    .Top(20)
    .ToListAsync();

var communities = await context.People.Query()
    .Analyze(context.Friendships)
    .Louvain(new LouvainOptions(MaximumLevels: 8))
    .OnProjection("social")
    .ToListAsync();

The analytics contract covers the full centrality and community-detection families and preserves algorithm-specific metrics for HITS, bridges, components, cliques, clustering, and modularity. Neo4j uses explicitly enabled GDS procedures; TigerGraph advertises only explicitly configured installed GSQL query endpoints. Unsupported operations fail before transport execution and are never emulated by downloading the graph into application memory.

Shortest paths keep both endpoints strongly typed:

GraphRoute<Person, Knows> route = await context.People
    .Match(person => person.Id == sourceId)
    .ShortestPathTo(context.People.Match(person => person.Id == targetId), context.Friendships)
    .MaxDepth(8)
    .SingleAsync();

Neo4j GDS deployments expose discovery and projection create/reuse/drop operations through context.Database.GetAnalyticsRuntime(). TigerGraph exposes its configured installed-query snapshot through the same segregated runtime contract.

Hot query factories can be compiled once:

var personById = NodalCompiledQuery.Compile((SocialGraphContext database, string id) =>
    database.People.Match(person => person.Id == id));

var ada = await personById(context, "person-42").SingleAsync();

Graph-native queries support incoming, outgoing, and undirected hops, repeated-hop depth bounds, multiple compatible edge types, explicit cycle policy, and normalized subgraph output:

GraphQueryResult neighborhood = await context.People
    .Match(person => person.Id == "person-42")
    .Traverse(context.Friendships, minDepth: 1, maxDepth: 3)
    .WithoutCycles()
    .ToSubgraphAsync();

Neo4j compiles repeated hops and simple paths to Cypher. TigerGraph switches only repeated-hop queries to GSQL Syntax V2 and keeps fixed traversals on the stable Syntax V1 path. Unsupported semantic combinations fail explicitly: TigerGraph does not emulate optional match or a vertex-simple variable-depth path when GSQL cannot expose the required intermediate aliases.

Provider-native escape hatches remain parameterized and return the same normalized result contracts:

var rawPeople = await context.Database.QueryRawAsync<Person>(
    "MATCH (`node`:`Person`) WHERE `node`.`person_id` = $id RETURN `node`",
    new Dictionary<string, object?> { ["id"] = "person-42" });

Use a path projection when the relationship payload is part of the domain operation:

var path = await context.People
    .Match(person => person.Id == "person-42")
    .TraversePath(context.Friendships)
    .WhereRelation(knows => knows.Since >= new DateTime(2024, 1, 1))
    .WhereTarget(person => person.Name != "Grace")
    .SingleAsync();

Console.WriteLine(path.Source);
Console.WriteLine(path.Relation);
Console.WriteLine(path.Target);

Path nodes and relationships participate in the context identity map. Repeating the query therefore reuses tracked instances. A mutable relationship payload can be persisted through the same unit of work:

path.Relation.Since = DateTime.UtcNow;
context.Friendships.Update(path.Source, path.Relation, path.Target);
await context.SaveChangesAsync();

ToRelationsAsync() is available when only relationship payloads are needed. Neo4j updates a queried relationship by its exact provider element identity, preserving parallel edges. TigerGraph emits source, edge, and target output through typed GSQL accumulators and normalizes the response to the same canonical path records.

Provider construction

Neo4j uses one long-lived official driver instance and therefore reuses its Bolt connection pool:

await using var provider = new Neo4jProvider(new Neo4jOptions
{
    Endpoint = new Uri("neo4j://localhost:7687"),
    Username = "neo4j",
    Password = "secret",
    Database = "neo4j"
});

var context = new SocialGraphContext(provider);

TigerGraph uses an externally managed HttpClient, allowing applications to control HTTP pooling and handler lifetime:

var httpClient = new HttpClient();
var provider = new TigerGraphProvider(
    httpClient,
    new TigerGraphOptions
    {
        Endpoint = new Uri("https://example.i.tgcloud.io/"),
        Username = "tigergraph",
        Password = "secret"
    },
    graphName: "SocialGraph");

var context = new SocialGraphContext(provider);

Both providers produce Nodal's canonical result model before POCO materialization. Provider-specific response shapes therefore do not escape into application code.

Unit of work

Node and relationship mutations are collected in the context and converted into an ordered, provider-neutral mutation plan:

var ada = new Person("person-1", "Ada");
var alan = new Person("person-2", "Alan");

context.People.Add(ada);
context.People.Add(alan);
context.Friendships.Connect(ada, new Knows(DateTime.UtcNow), alan);

GraphSaveResult result = await context.SaveChangesAsync();

New nodes are planned before their relationships. Relationship deletions are planned before node deletions. Entry states are accepted only after the provider confirms a successful commit; a failed commit therefore remains retryable. Providers opt into writes through IGraphMutationProvider, allowing read-only providers to remain valid.

Neo4j executes the full plan inside a client-managed write transaction. TigerGraph executes vertex and edge upserts as one REST transaction with gsql-atomic-level: atomic; its capability metadata therefore reports RequestOrQuery transaction scope. When a plan contains a deletion, Nodal derives a deterministic query name from the operation shape, creates and installs one parameterized nodal_apply_mutations_* GSQL query through the configured administrative transport, and invokes its REST endpoint once. Later plans with the same shape reuse that installed query. A definition or installation failure occurs before the data endpoint is called, and Nodal never splits one unit of work into non-atomic requests.

Pending work can be inspected without exposing provider-specific commands:

var pending = context.ChangeTracker.Entries(GraphEntryState.Added);

Queries use identity resolution by default. Tracked mutable POCOs are compared with their original mapped-property snapshots when SaveChangesAsync runs, so calling Update is not required for ordinary property edits. AutoDetectChangesEnabled, DetectChanges, Entry, Attach, Detach, property-level IsModified, AsNoTracking, and ReloadAsync provide explicit control for high-volume workloads.

Migrations

Migrations declare portable schema intent and carry a stable history identifier:

public sealed class InitialSocialGraph : NodalMigration
{
    public override string Id => "20260816_001_initial_social_graph";

    protected override void Up(MigrationBuilder migration) => migration
        .CreateNode<Person>()
        .CreateRelation<Knows, Person, Person>()
        .CreateIndex<Person, string>(person => person.Name);

    protected override void Down(MigrationBuilder migration) => migration
        .DropRelation<Knows>()
        .DropNode<Person>();
}

Planning is side-effect free. Execution skips identifiers already stored in provider history:

NodalMigration[] migrations = [new InitialSocialGraph()];

MigrationPlan dryRun = await context.Database.PlanMigrationsAsync(migrations);
MigrationPlan applied = await context.Database.MigrateAsync(migrations);

Plans contain deterministic SHA-256 checksums and provider-specific commands. Neo4j commits a homogeneous schema-command batch transactionally, then records the __NodalMigration state in a separate graph-write transaction because Neo4j does not permit schema modifications and graph writes in the same transaction. Nodal uses an Applying/Applied/Failed state machine and idempotent DDL so interrupted schema work is visible and retryable. TigerGraph compiles typed vertex, edge, and secondary-index operations into one deterministic schema-change job. TigerGraph administrative execution remains an explicit provider capability because its supported REST API exposes schema inspection and query installation but not a general arbitrary-DDL endpoint; the framework does not silently invent or depend on an undocumented route.

TigerGraph migration execution is enabled only when the host supplies an administrative transport appropriate to its deployment. Self-managed and local Docker installations can use the included documented GSQL process transport:

ITigerGraphAdministrativeControlPlane administration = new TigerGraphGsqlProcessTransport(
    new TigerGraphGsqlProcessOptions
    {
        FileName = "docker",
        PrefixArguments =
        [
            "exec",
            "nodal-tigergraph",
            "/home/tigergraph/tigergraph/app/4.2.4/cmd/gsql"
        ],
        GraphName = "SocialGraph",
        VerifiedServerVersion = "4.2.4 Community"
    });
var provider = new TigerGraphProvider(
    httpClient,
    tigerGraphOptions,
    "SocialGraph",
    administration);

Migration support is advertised only after the control plane verifies schema read/write, job inspection, cleanup, and graph-scoped locking. The executor bootstraps __NodalMigration plus an independent __NodalSchemaJob journal, records every irreversible phase, and performs temporary-job cleanup with a bounded token independent from caller cancellation. A restart resumes cleanup or history persistence without replaying a schema change known to have succeeded. A cancelled RUN has an unknown outcome and throws TigerGraphMigrationRecoveryRequiredException until an operator inspects the graph and calls provider.MigrationRecovery.ConfirmSchemaAppliedAsync(...) or ConfirmSchemaNotAppliedAsync(...).

The same administrative channel enables lazy installation of transactional mutation queries required by delete-containing units of work. Without it, querying and atomic create/update batches remain available while migrations and delete plans report an explicit unsupported-capability error. Because mutation dictionaries currently carry runtime values rather than declared property metadata, a null property in a delete-containing compiled plan is rejected instead of guessing an unsafe GSQL parameter type.

Documentation

The documentation platform combines an English Docusaurus guide and journal with a DocFX API reference generated from the product's XML documentation. Machine-readable llms.txt, extended coding-agent context, and a JSON-LD capability graph are published with the static site.

Build the complete site locally with:

powershell -NoProfile -ExecutionPolicy Bypass -File ./eng/build-docs.ps1

The production output is written to website/build. During authoring, restore the website packages and start the live Docusaurus server with:

npm.cmd ci --prefix website
npm.cmd run start --prefix website

API pages under /api are generated by DocFX before the Docusaurus build. See docs/DEPLOYMENT.md for the Cloudflare Pages setup and required GitHub environment secrets.

Quality gate

The repository has one local command matching the CI quality job:

powershell -NoProfile -ExecutionPolicy Bypass -File ./eng/verify.ps1

It restores dependencies, verifies formatting, builds in Release mode, runs the complete test suite, enforces at least 95% line coverage for every governed production package, and validates the publishable NuGet archives. Coverage can also be run independently:

powershell -NoProfile -ExecutionPolicy Bypass -File ./eng/verify-coverage.ps1

The script rebuilds each governed package's tests, produces Cobertura reports under the ignored TestResults directory, and fails when any package falls below the threshold.

Package verification can also be run independently:

powershell -NoProfile -ExecutionPolicy Bypass -File ./eng/verify-packages.ps1

The package gate produces all nine .nupkg and .snupkg artifacts, then inspects their manifests and contents for the MPL-2.0 expression, repository metadata, README, license, IntelliSense XML, target framework, and required package dependencies.

Publishing

Beta packages are published only after a pull request promotes developer to staging. The Publish Beta Packages workflow assigns one immutable 0.1.0-beta.<run> version to all nine packages, runs the complete QA gate, generates release evidence and an SPDX SBOM, attests package provenance, exchanges GitHub's OIDC identity for a short-lived NuGet credential, and publishes Nodal.Core before its dependent packages. No long-lived NuGet API key is stored by the repository.

After publication, the same workflow runs a clean-room World Food Delivery consumer smoke test. It copies a small CSV order dataset into a fresh temporary console application, restores only the immutable packages from NuGet.org, imports customers, restaurants, foods, orders, couriers, and relationship payloads in one bounded unit of work, and validates migration planning plus Neo4j and TigerGraph query boundaries. The consumer project contains no ProjectReference; its resolved package identities are retained as a workflow artifact. This verifies the experience an external application receives, rather than merely rebuilding this repository.

The GitHub staging environment must define NUGET_USER as the NuGet profile name. NuGet Trusted Publishing must match repository owner Greenstone-Research-Lab, repository NodalFramework, workflow file publish-alpha.yml, and environment staging. The historical workflow filename is retained because it forms part of the existing trusted-publishing identity; its workflow and job names now describe the beta channel. Publishing deliberately does not use --skip-duplicate, ensuring package conflicts and reserved identifiers fail visibly.

Live integration tests

Live database tests are isolated in Nodal.IntegrationTests and are skipped during ordinary unit-test runs unless their environment is configured.

Local Docker stack

Neo4j and TigerGraph Community can be started together as a persistent local development stack:

powershell -NoProfile -ExecutionPolicy Bypass -File ./eng/start-local-databases.ps1

Neo4j Browser is available at http://localhost:7474 with local-only credentials neo4j / NodalLocal123!. TigerGraph GraphStudio and its consolidated REST/GSQL endpoint are available at http://localhost:14240 with the Community image's local credentials tigergraph / tigergraph; the startup script creates the NodalQa graph with the Person vertex and KNOWS edge schema used by the integration suite. TigerGraph is substantially larger than Neo4j and requires at least 8 GB of Docker memory.

Run both live provider suites against the persistent containers with:

powershell -NoProfile -ExecutionPolicy Bypass -File ./eng/run-local-integration.ps1

Stop containers while preserving their data volumes with:

powershell -NoProfile -ExecutionPolicy Bypass -File ./eng/stop-local-databases.ps1

The compose ports and credentials are intentionally limited to loopback-bound local development. REST++ data authentication is disabled by the Community image's local configuration, while interpreted GSQL requests use Basic authentication. These settings must not be reused for shared or production deployments.

Runnable provider demos

The samples directory contains one shared social graph model and two console hosts. Both hosts execute the same provider-neutral create, path traversal, update, and verification workflow; only provider construction differs.

With the local Docker stack available, run both demos using:

powershell -NoProfile -ExecutionPolicy Bypass -File ./eng/run-local-demos.ps1

The generated Ada -> KNOWS -> Alan paths remain in each database for visual inspection. Connection settings can be overridden through the documented NODAL_NEO4J_* and NODAL_TIGERGRAPH_* environment variables.

Neo4j can be started in a disposable Docker container and tested end to end with:

powershell -NoProfile -ExecutionPolicy Bypass -File ./eng/run-neo4j-integration.ps1

The runner publishes a temporary Bolt port, supplies credentials only through process environment variables, runs commit/read/delete and rollback tests, and removes its uniquely named container in a finally block.

TigerGraph live tests expect a dedicated QA graph containing Person vertices and KNOWS edges with the properties used by the sample model:

$env:NODAL_TIGERGRAPH_ENDPOINT = 'https://your-host/'
$env:NODAL_TIGERGRAPH_ACCESS_TOKEN = 'secret-token'
$env:NODAL_TIGERGRAPH_GRAPH = 'NodalQa'
powershell -NoProfile -ExecutionPolicy Bypass -File ./eng/run-tigergraph-integration.ps1

No live credentials are stored in the repository. The integration project intentionally avoids the current Testcontainers dependency chain until its high-severity SSH.NET advisory is available through a patched NuGet release.

GitHub Actions runs the quality gate and a disposable Neo4j smoke environment for every pull request targeting developer, staging, or master. TigerGraph smoke tests run only outside pull requests when the repository variable NODAL_RUN_TIGERGRAPH is true; credentials are read from the protected tigergraph-qa environment secrets NODAL_TIGERGRAPH_ENDPOINT, NODAL_TIGERGRAPH_ACCESS_TOKEN, and NODAL_TIGERGRAPH_GRAPH. The TigerGraph suite verifies create/read/update persistence and confirms that an invalid edge rolls back vertices in an atomic REST batch.

License

Nodal Framework source code is distributed under the MPL-2.0 license. Documentation is distributed under CC BY 4.0. Trademarks and hosted or commercial services are governed by the policies in this repository.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.1.0-beta.14 71 8/29/2026
0.1.0-beta.12 80 8/28/2026
0.1.0-alpha.11 75 8/27/2026
0.1.0-alpha.10 60 8/27/2026
0.1.0-alpha.9 66 8/27/2026
0.1.0-alpha.8 63 8/27/2026
0.1.0-alpha.7 68 8/26/2026
0.1.0-alpha.6 62 8/26/2026