{"id":37045128,"url":"https://github.com/ulaserkus/fluent-dynamics","last_synced_at":"2026-01-14T05:12:55.685Z","repository":{"id":310628438,"uuid":"1040295539","full_name":"ulaserkus/fluent-dynamics","owner":"ulaserkus","description":"FluentDynamics QueryBuilder is a fluent, chainable API for building and executing Dynamics 365/Dataverse queries. It simplifies the process of creating complex QueryExpressions with a more intuitive and readable syntax.","archived":false,"fork":false,"pushed_at":"2025-08-22T14:37:34.000Z","size":401,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"v1.1.x","last_synced_at":"2025-12-05T14:44:09.141Z","etag":null,"topics":["dataverse","dataverse-api","dynamics","dynamics-365","dynamics-crm","fluent","fluent-queryexpression","powerplattform","query-builder","queryexpression","queryexpression-builder"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/FluentDynamics.QueryBuilder/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ulaserkus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-18T18:52:50.000Z","updated_at":"2025-09-04T12:46:10.000Z","dependencies_parsed_at":"2025-08-21T21:06:36.914Z","dependency_job_id":null,"html_url":"https://github.com/ulaserkus/fluent-dynamics","commit_stats":null,"previous_names":["ulaserkus/fluent-dynamics"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ulaserkus/fluent-dynamics","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulaserkus%2Ffluent-dynamics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulaserkus%2Ffluent-dynamics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulaserkus%2Ffluent-dynamics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulaserkus%2Ffluent-dynamics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ulaserkus","download_url":"https://codeload.github.com/ulaserkus/fluent-dynamics/tar.gz/refs/heads/v1.1.x","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulaserkus%2Ffluent-dynamics/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28410312,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dataverse","dataverse-api","dynamics","dynamics-365","dynamics-crm","fluent","fluent-queryexpression","powerplattform","query-builder","queryexpression","queryexpression-builder"],"created_at":"2026-01-14T05:12:55.090Z","updated_at":"2026-01-14T05:12:55.679Z","avatar_url":"https://github.com/ulaserkus.png","language":"C#","readme":"# FluentDynamics QueryBuilder\n\nFluentDynamics QueryBuilder is a fluent, chainable API for building and executing Dynamics 365/Dataverse queries. It simplifies the process of creating complex QueryExpressions with a more intuitive and readable syntax.\n\n[![NuGet](https://img.shields.io/nuget/v/FluentDynamics.QueryBuilder.svg)](https://www.nuget.org/packages/FluentDynamics.QueryBuilder/)\n[![License](https://img.shields.io/github/license/ulaserkus/fluent-dynamics)](LICENSE)\n![Line Coverage](https://img.shields.io/badge/line%20coverage-85.35%25-brightgreen)\n![Branch Coverage](https://img.shields.io/badge/branch%20coverage-72.72%25-yellow)\n![Method Coverage](https://img.shields.io/badge/method%20coverage-94.55%25-brightgreen)\n![Tests](https://img.shields.io/badge/tests-180%20passed-brightgreen)\n\n## Features\n\n- 🔄 **Fluent API** - Chainable, intuitive query building\n- 🔍 **Type-safe** - Strong typing for Dynamics 365 operations\n- 🚀 **Async Support** - Full support for async/await patterns\n- 📊 **LINQ-like Operations** - Familiar extension methods for query results\n- 📑 **Pagination** - Built-in support for handling paged results\n- 🔗 **Complex \u0026 Advanced Joins** - Rich set of join helpers (Inner, LeftOuter, Natural, CrossApply, In, Exists, Any/NotAny, All/NotAll)\n- 🧩 **Extensible** - Clean architecture for extending functionality\n- 🛠 **FetchXML Conversion** - Convert queries to FetchXML easily\n- 🧮 **Distinct, NoLock, QueryHint, ForceSeek** - Advanced query options\n- ⚡ **FilterBuilder Extensions** - Syntactic sugar methods for common conditions (Equal, In, Like, LastXDays, IsNull, etc.)\n- 🔍 **Query Debugging** - Human-readable query inspection with DebugView\n- 🔄 **Optimized Cloning** - Efficient query cloning operations for different use cases\n\n## Installation\n\nInstall via NuGet Package Manager:\n\n```\nInstall-Package FluentDynamics.QueryBuilder\n```\n\nOr via .NET CLI:\n\n```\ndotnet add package FluentDynamics.QueryBuilder\n```\n\n## Basic Usage\n\n```csharp\nusing FluentDynamics.QueryBuilder;\nusing Microsoft.Xrm.Sdk;\nusing Microsoft.Xrm.Sdk.Query;\n\n// Create a simple query\nvar query = Query.For(\"account\")\n    .Select(\"name\", \"accountnumber\", \"telephone1\")\n    .Where(f =\u003e f\n        .Condition(\"statecode\", ConditionOperator.Equal, 0)\n    )\n    .OrderBy(\"name\");\n\n// Execute the query\nEntityCollection results = query.RetrieveMultiple(organizationService);\n\n// Use extension methods on results\nvar accounts = results.ToList();\n```\n\n## Advanced Examples\n\n### Complex Filtering (Nested AND/OR)\n\n```csharp\nvar query = Query.For(\"contact\")\n    .Select(\"firstname\", \"lastname\", \"emailaddress1\")\n    .Where(f =\u003e f\n        .Condition(\"statecode\", ConditionOperator.Equal, 0)\n        .And(fa =\u003e fa\n            .Condition(\"createdon\", ConditionOperator.LastXDays, 30)\n            .Condition(\"emailaddress1\", ConditionOperator.NotNull)\n        )\n        .Or(fo =\u003e fo\n            .Condition(\"parentcustomerid\", ConditionOperator.Equal, accountId)\n            .Condition(\"address1_city\", ConditionOperator.Equal, \"Seattle\")\n        )\n    )\n    .OrderBy(\"lastname\")\n    .OrderBy(\"firstname\");\n```\n\n### Complex Filtering Using FilterBuilder Extensions\n\n```csharp\nvar query = Query.For(\"contact\")\n    .Select(\"firstname\", \"lastname\", \"emailaddress1\")\n    .Where(f =\u003e f\n        .Equal(\"statecode\", 0)\n        .And(fa =\u003e fa\n            .LastXDays(\"createdon\", 30)\n            .IsNotNull(\"emailaddress1\")\n        )\n        .Or(fo =\u003e fo\n            .Equal(\"parentcustomerid\", accountId)\n            .Equal(\"address1_city\", \"Seattle\")\n        )\n    )\n    .OrderBy(\"lastname\")\n    .OrderBy(\"firstname\");\n```\n\n### Joining Entities (Link Entities)\n\n```csharp\nvar query = Query.For(\"opportunity\")\n    .Select(\"name\", \"estimatedvalue\", \"closeprobability\")\n    .Where(f =\u003e f\n        .Condition(\"statecode\", ConditionOperator.Equal, 0)\n    )\n    .Link(\"account\", \"customerid\", \"accountid\", JoinOperator.Inner, link =\u003e {\n        link.Select(\"name\", \"accountnumber\")\n            .As(\"account\")\n            .Where(f =\u003e f\n                .Condition(\"statecode\", ConditionOperator.Equal, 0)\n            );\n    })\n    .Link(\"contact\", \"customerid\", \"contactid\", JoinOperator.LeftOuter, link =\u003e {\n        link.Select(\"fullname\", \"emailaddress1\")\n            .As(\"contact\");\n    });\n```\n\n---\n\n## Advanced Join Extensions\n\nThe library exposes helper extension methods (in `QueryBuilderExtensions`) for additional Dataverse `JoinOperator` values. These allow more expressive and often more performant queries in certain scenarios.\n\n| Extension Method | Underlying JoinOperator | Purpose / Behavior |\n|------------------|-------------------------|--------------------|\n| `InnerJoin` | `Inner` | Standard inner join (matching related rows only) |\n| `LeftOuterJoin` | `LeftOuter` | Include parent even if no related rows exist |\n| `NaturalJoin` | `Natural` | Natural join (rarely used; relies on platform semantics) |\n| `CrossApplyJoin` | `MatchFirstRowUsingCrossApply` | Optimizes when you only need the first matching child row |\n| `InJoin` | `In` | Translates to `IN` semantics; can improve performance on large link sets |\n| `ExistsJoin` | `Exists` | Uses `EXISTS` logic to restrict parents to those having matches |\n| `AnyJoin` | `Any` | Parent rows where any related rows (after link filters) exist |\n| `NotAnyJoin` | `NotAny` | Parent rows where no related rows (after link filters) exist |\n| `AllJoin` | `All` | Parents with related rows where none of those rows satisfy additional filters (logical “ALL NOT”) |\n| `NotAllJoin` | `NotAll` | Alias of `Any` semantics (Dataverse quirk) |\n\n\u003e Note: Some of these operators can significantly change the result semantics and/or execution plan. Always validate with real data and inspect the generated FetchXML (via `ToFetchExpression`) or use `DebugView()` for clarity.\n\n### When to Use Which Advanced Join\n\n- Use `CrossApplyJoin` to fetch a single \"primary\" child row (e.g., latest activity) without bringing an entire child set.\n- Use `ExistsJoin` / `AnyJoin` when you want to check for the presence of related rows without needing their data.\n- Use `NotAnyJoin` to enforce absence of related data (e.g. accounts without open opportunities).\n- Use `InJoin` when a sub-select style inclusion might be faster than a traditional join (test vs inner join).\n- Use `AllJoin` when you need parent rows where all related rows fail a condition expressed in the filters (Dataverse implements this as “all related but none meet filter X”).\n\n### Code Examples\n\n#### 1. Any vs NotAny (Presence / Absence)\n\n```csharp\n// Accounts that HAVE at least one active contact\nvar accountsWithActiveContact = Query.For(\"account\")\n    .AnyJoin(\"contact\", \"accountid\", \"parentcustomerid\", link =\u003e link\n        .Where(f =\u003e f.Equal(\"statecode\", 0))\n    );\n\n// Accounts that have NO active contact\nvar accountsWithoutActiveContact = Query.For(\"account\")\n    .NotAnyJoin(\"contact\", \"accountid\", \"parentcustomerid\", link =\u003e link\n        .Where(f =\u003e f.Equal(\"statecode\", 0))\n    );\n```\n\n#### 2. Exists vs In\n\n```csharp\n// Accounts that have at least one open opportunity (using EXISTS)\nvar accountsWithOpportunitiesExists = Query.For(\"account\")\n    .ExistsJoin(\"opportunity\", \"accountid\", \"parentaccountid\", link =\u003e link\n        .Where(f =\u003e f.Equal(\"statecode\", 0))\n    );\n\n// Accounts using IN-based restriction (platform may choose different plan)\nvar accountsWithOpportunitiesIn = Query.For(\"account\")\n    .InJoin(\"opportunity\", \"accountid\", \"parentaccountid\", link =\u003e link\n        .Where(f =\u003e f.Equal(\"statecode\", 0))\n    );\n```\n\n#### 3. CrossApply (First Matching Child)\n\n```csharp\n// Get account with only ONE (first) recent task (improves performance vs full child set)\nvar accountsWithOneTask = Query.For(\"account\")\n    .CrossApplyJoin(\"task\", \"accountid\", \"regardingobjectid\", link =\u003e link\n        .Select(\"subject\", \"scheduledend\")\n        .OrderByDesc(\"scheduledend\")  // Ensure desired ordering for \"first\"\n        .Top(1) // (Optional) If you later add Top to the link (manual pattern)\n    );\n```\n\n\u003e Dataverse’s cross-apply logic tries to retrieve just the first qualifying child row; ensure your sorting reflects the intended “first”.\n\n#### 4. All / NotAll Semantics\n\n```csharp\n// Accounts where related opportunities exist, but NONE of those are still open\nvar accountsWhereAllOpportunitiesClosed = Query.For(\"account\")\n    .AllJoin(\"opportunity\", \"accountid\", \"parentaccountid\", link =\u003e link\n        .Where(f =\u003e f.Equal(\"statecode\", 0)) // Filter defines \"open\" – ALL join returns parents where no linked rows satisfy this\n    );\n\n// NotAll is effectively Any (platform quirk)\nvar accountsWithAnyOpenOpportunity = Query.For(\"account\")\n    .NotAllJoin(\"opportunity\", \"accountid\", \"parentaccountid\", link =\u003e link\n        .Where(f =\u003e f.Equal(\"statecode\", 0))\n    );\n```\n\n#### 5. Combining Multiple Advanced Joins\n\n```csharp\nvar complex = Query.For(\"account\")\n    .Select(\"name\")\n    .AnyJoin(\"contact\", \"accountid\", \"parentcustomerid\", c =\u003e c\n        .Where(f =\u003e f.Equal(\"statecode\", 0))\n        .Select(\"fullname\")\n    )\n    .NotAnyJoin(\"opportunity\", \"accountid\", \"parentaccountid\", o =\u003e o\n        .Where(f =\u003e f.Equal(\"statecode\", 0)) // Has no active opportunities\n    )\n    .CrossApplyJoin(\"task\", \"accountid\", \"regardingobjectid\", t =\u003e t\n        .Select(\"subject\")\n        .OrderByDesc(\"createdon\")\n    );\n```\n\n### Performance Tips\n\n| Scenario | Recommended Join Helper | Rationale |\n|----------|-------------------------|-----------|\n| Need only first child row | `CrossApplyJoin` | Avoids pulling all children |\n| Presence check only | `ExistsJoin` or `AnyJoin` | Clear semantic \u0026 often cheaper |\n| Absence check | `NotAnyJoin` | Efficient anti-semi logic |\n| None of children meet condition | `AllJoin` | Expresses universal negative |\n| Explore optimizer difference | Compare `InnerJoin` vs `InJoin` | Sometimes IN variant changes plan |\n| Filtering parent by complex related predicate | `ExistsJoin` | Moves predicate to EXISTS scope |\n\nUse `DebugView()` during development:\n\n```csharp\nvar debug = complex.DebugView();\nConsole.WriteLine(debug);\n```\n\n---\n\n## Pagination \u0026 Async\n\n```csharp\n// Get a specific page\nvar page2 = query.RetrieveMultiple(service, pageNumber: 2, pageSize: 50);\n\n// Retrieve all pages automatically\nvar allResults = query.RetrieveMultipleAllPages(service);\n\n// Using async version\nvar results = await query.RetrieveMultipleAsync(service);\n\n// Async with pagination\nvar pageResults = await query.RetrieveMultipleAsync(service, pageNumber: 2, pageSize: 50);\n\n// Async all pages\nvar allAsyncResults = await query.RetrieveMultipleAllPagesAsync(service);\n```\n\n## FetchXML Conversion\n\n```csharp\n// Convert QueryExpression to FetchXML\nvar fetchXml = query.ToFetchExpression(service);\n```\n\n## Working with Results\n\n```csharp\n// Convert to list\nvar entities = results.ToList();\n\n// Filter results\nvar filteredEntities = results.Where(e =\u003e e.Contains(\"emailaddress1\"));\n\n// Project to new form\nvar names = results.Select(e =\u003e e.GetAttributeValue\u003cstring\u003e(\"name\"));\n\n// Get first matching entity\nvar matchingContact = results.FirstOrDefault(e =\u003e \n    e.GetAttributeValue\u003cstring\u003e(\"emailaddress1\")?.Contains(\"example.com\") == true);\n\n// Safe attribute access\nstring name = entity.TryGet\u003cstring\u003e(\"name\", \"Default Name\");\n```\n\n## API Reference\n\n### Query\nEntry point for building queries:\n- `Query.For(entityName)` - Creates a new query for the specified entity\n\n### QueryExpressionBuilder\nMethods for configuring the main query:\n- `Select(params string[] attributes)` - Specifies columns to include\n- `SelectAll()` - Includes all columns\n- `Where(Action\u003cFilterBuilder\u003e filterConfig)` - Adds a filter group using fluent configuration\n- `OrderBy(attribute)` - Adds ascending a sort order\n- `OrderByDesc(attribute)` - Adds descending a sort order\n- `Link(toEntity, fromAttribute, toAttribute, joinType, Action\u003cLinkEntityBuilder\u003e linkBuilder)` - Adds a join\n- `Top(count)` - Limits the number of records\n- `Distinct()` - Returns only distinct records\n- `NoLock()` - Uses NOLOCK hint\n- `QueryHint(hint)` - Adds a query hint\n- `ForceSeek(indexName)` - Forces using a specific index\n\n### Execution Methods\n- `RetrieveMultiple(service)`\n- `RetrieveMultiple(service, pageNumber, pageSize)`\n- `RetrieveMultipleAllPages(service)`\n- `Exists(service)`\n- `RetrieveMultipleAsync(service, CancellationToken cancellationToken = default)`\n- `RetrieveMultipleAsync(service, pageNumber, pageSize, CancellationToken cancellationToken = default)`\n- `RetrieveMultipleAllPagesAsync(service, CancellationToken cancellationToken = default)`\n- `ExistsAsync(service, CancellationToken cancellationToken = default)`\n- `ToQueryExpression()`\n- `ToFetchExpression(service)`\n\n### FilterBuilder\nBuilds complex filter logic:\n- `Condition(attribute, operator, value)`\n- `And(Action\u003cFilterBuilder\u003e nested)`\n- `Or(Action\u003cFilterBuilder\u003e nested)`\n- `ToExpression()`\n\n### FilterBuilder Extensions (Syntactic Sugar)\nConvenience methods mapping to common `ConditionOperator` values. They improve readability and reduce verbosity.\n\n| Extension | Purpose | Equivalent |\n|-----------|---------|-----------|\n| `Equal(attr, value)` | Equality | `Condition(attr, Equal, value)` |\n| `NotEqual(attr, value)` | Inequality | `Condition(attr, NotEqual, value)` |\n| `GreaterThan(attr, value)` | Greater than | `Condition(attr, GreaterThan, value)` |\n| `GreaterEqual(attr, value)` | Greater or equal | `Condition(attr, GreaterEqual, value)` |\n| `LessThan(attr, value)` | Less than | `Condition(attr, LessThan, value)` |\n| `LessEqual(attr, value)` | Less or equal | `Condition(attr, LessEqual, value)` |\n| `Like(attr, pattern)` | SQL-like pattern | `Condition(attr, Like, pattern)` |\n| `NotLike(attr, pattern)` | Negated like | `Condition(attr, NotLike, pattern)` |\n| `BeginsWith(attr, value)` | Prefix | `Condition(attr, BeginsWith, value)` |\n| `EndsWith(attr, value)` | Suffix | `Condition(attr, EndsWith, value)` |\n| `Contains(attr, value)` | Contains text | `Condition(attr, Contains, value)` |\n| `In(attr, params values)` | In list | `Condition(attr, In, valuesArray)` |\n| `NotIn(attr, params values)` | Not in list | `Condition(attr, NotIn, valuesArray)` |\n| `Between(attr, from, to)` | Between range | `Condition(attr, Between, new[]{from,to})` |\n| `NotBetween(attr, from, to)` | Not between | `Condition(attr, NotBetween, new[]{from,to})` |\n| `IsNull(attr)` | Null check | `Condition(attr, Null, null)` |\n| `IsNotNull(attr)` | Not null | `Condition(attr, NotNull, null)` |\n| `LastXDays(attr, days)` | Relative date | `Condition(attr, LastXDays, days)` |\n| `NextXDays(attr, days)` | Relative date | `Condition(attr, NextXDays, days)` |\n| `LastXMonths(attr, months)` | Relative date | `Condition(attr, LastXMonths, months)` |\n| `NextXMonths(attr, months)` | Relative date | `Condition(attr, NextXMonths, months)` |\n| `LastXYears(attr, years)` | Relative date | `Condition(attr, LastXYears, years)` |\n| `NextXYears(attr, years)` | Relative date | `Condition(attr, NextXYears, years)` |\n| `On(attr, date)` | Specific date | `Condition(attr, On, date)` |\n| `OnOrBefore(attr, date)` | On or before | `Condition(attr, OnOrBefore, date)` |\n| `OnOrAfter(attr, date)` | On or after | `Condition(attr, OnOrAfter, date)` |\n\nExample:\n\n```csharp\nvar q = Query.For(\"contact\")\n    .Select(\"firstname\", \"lastname\", \"emailaddress1\", \"createdon\")\n    .Where(f =\u003e f\n        .Equal(\"statecode\", 0)\n        .IsNotNull(\"emailaddress1\")\n        .LastXDays(\"createdon\", 30)\n        .Or(o =\u003e o\n            .Like(\"emailaddress1\", \"%@example.com\")\n            .In(\"address1_city\", \"Seattle\", \"London\", \"Berlin\")\n        )\n    );\n```\n\n### LinkEntityBuilder\nConfigures join/link entities:\n- `Select(params string[] attributes)`\n- `SelectAll()`\n- `As(alias)`\n- `OrderBy(attribute)`\n- `OrderByDesc(attribute)`\n- `Where(Action\u003cFilterBuilder\u003e filterConfig)`\n- `Link(toEntity, fromAttribute, toAttribute, joinType, Action\u003cLinkEntityBuilder\u003e linkBuilder)`\n\n### Advanced Join Helpers (QueryBuilderExtensions)\n- `InnerJoin(...)`\n- `LeftOuterJoin(...)`\n- `NaturalJoin(...)`\n- `CrossApplyJoin(...)`\n- `InJoin(...)`\n- `ExistsJoin(...)`\n- `AnyJoin(...)`\n- `NotAnyJoin(...)`\n- `AllJoin(...)`\n- `NotAllJoin(...)`\n\n### Extension Methods (LINQ-like)\n- `ToList()` / `ToArray()`\n- `FirstOrDefault(predicate)`\n- `SingleOrDefault(predicate)`\n- `Where(predicate)`\n- `Select(selector)`\n- `TryGet\u003cT\u003e(attributeName, defaultValue)`\n- `DeepClone()`\n- `ShallowClone()`\n- `CloneForPagination()`\n- `DebugView()`\n\n---\n\n### Module Coverage\n| Module | Line | Branch | Method |\n|--------|------|--------|--------|\n| FluentDynamics.QueryBuilder | 85.35% | 72.72% | 94.55% |\n\n### Overall Coverage\n| Metric | Line | Branch | Method |\n|--------|------|--------|--------|\n| Total | 85.35% | 72.72% | 94.55% |\n| Average | 85.35% | 72.72% | 94.55% |\n\n### Test Summary\n- Total Tests: 180\n- Failed: 0\n- Succeeded: 180\n- Skipped: 0\n- Duration: 2.5s\n\n---\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n---\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fulaserkus%2Ffluent-dynamics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fulaserkus%2Ffluent-dynamics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fulaserkus%2Ffluent-dynamics/lists"}