{"id":22841355,"url":"https://github.com/romaninsh/vantage","last_synced_at":"2025-04-28T11:15:51.099Z","repository":{"id":249483231,"uuid":"831647286","full_name":"romaninsh/vantage","owner":"romaninsh","description":"Vantage rust crate implements a simple way for your business app to interact with data (e.g. SQL)","archived":false,"fork":false,"pushed_at":"2025-03-19T00:35:15.000Z","size":2126,"stargazers_count":18,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-28T11:15:44.529Z","etag":null,"topics":["entity","framework","orm","query-builder","rust","sql"],"latest_commit_sha":null,"homepage":"https://romaninsh.github.io/vantage/","language":"Rust","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/romaninsh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2024-07-21T07:45:03.000Z","updated_at":"2025-04-21T00:06:32.000Z","dependencies_parsed_at":"2024-07-21T08:51:11.082Z","dependency_job_id":"c2a8d5c6-cc4a-421d-9f71-25cc6b1e2433","html_url":"https://github.com/romaninsh/vantage","commit_stats":null,"previous_names":["romaninsh/dorm","romaninsh/vantage"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romaninsh%2Fvantage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romaninsh%2Fvantage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romaninsh%2Fvantage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romaninsh%2Fvantage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/romaninsh","download_url":"https://codeload.github.com/romaninsh/vantage/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251302782,"owners_count":21567601,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["entity","framework","orm","query-builder","rust","sql"],"created_at":"2024-12-13T01:15:46.169Z","updated_at":"2025-04-28T11:15:51.092Z","avatar_url":"https://github.com/romaninsh.png","language":"Rust","readme":"# Vantage\n\n[![Book](https://github.com/romaninsh/vantage/actions/workflows/book.yaml/badge.svg)](https://romaninsh.github.io/vantage/)\n\nVantage is an **Entity framework** for Rust apps that implements an opinionated Model\nDriven Architecture.\n\nVantage makes Rust more suitable for writing Business software such as CRM, HR, ERP or Low Code apps\nwhere large number of entities (types representing business objects, like an 'Invoice') must hold\ncomplex relationship, attribute, validation and other business rules.\n\nVantage framework focuses on the following 3 areas:\n\n- **Entity definition** - Using Rust code, describe your logical business entities,\n  their attributes, relationships and business rules.\n- **Query Building** - Dynamically create SQL queries using SQL dialect of your choice\n  and utilise full range of database features. Queries are not strictly SQL - they can\n  be implemented for NoSQL databases, REST APIs or even GraphQL.\n- **Data Sets** - Implementation of a type that represents a set of records, stored\n  remotely. Data Sets can be filtered, joined, aggregated and manipulated in various ways.\n  Most operations will yield a new DataSet or will build a Query incapsulating all\n  the business logic.\n\nIt is important that all 3 parts are combined together and as a result - Vantage\nallows you to write very low amount of code to achieve complex business logic without\nsacrifising performance.\n\nVantage introduces a clean app architecture to your developer team, keeping them\nefficient and your code maintainable.\n\n## Defining Entities\n\nWhile ORM libraries like Diesel or SQLx will use your SQL structure as a base, Vantage\nallows you to define your entities entirely in Rust code, without boilerplate. You do not\nneed to keep your entities in sync with SQL schema. For example, consider the following\nstructure:\n\n```rust\n#[derive(Clone, Debug, Serialize, Deserialize, Default)]\nstruct Invoice {\n    id: i64,\n    client_name: String,\n    total: i64,\n}\n#[derive(Clone, Debug, Serialize, Deserialize, Default)]\nstruct InvoiceLine {\n    id: i64,\n    invoice_id: i64,\n    product_code: String,\n    quantity: i64,\n    price: i64,\n}\n\nimpl Entity for Invoice {}\nimpl Entity for InvoiceLine {}\n```\n\nThose structures are handy to use in Rust, but they do not map directly to SQL schema.\nFields `client_name`, `total` and `product_code` are behind joins and subqueries.\nThis is fully supported by Vantage - you can have several Rust structs for interfacing\nwith your business entities, depending on use-case.\n\nSee [bakery_model](bakery_model/src/) for more examples.\n\n```rust\nlet invoice: Invoice = Invoice::table().with_id(123).get_one().await?;\nprintln!(\"Invoice: {:?}\", invoice); // calculates total, client_name etc.\n```\n\n## Query Building\n\nIn pursuit of better performance developers of business apps often resort to writing\nentire queries with `sqlx`. While this may work for a small application, for a large\nproject you would want generic types, dynamic queries and a better way to use Rust\nautocomplete and type systems.\n\nVantage provides a way to express your SQL queries in native Rust - dynamically:\n\n```rust\nuse vantage::prelude::*; // sql::Query\n\nlet github_authors_and_teams = Query::new()\n    .with_table(\"dx_teams\", Some(\"t\".to_string()))\n    .with_field(\"team_source_id\".to_string(), expr!(\"t.source_id\"));\n\n// Team is an anchestor\nlet github_authors_and_teams = github_authors_and_teams.with_join(query::JoinQuery::new(\n    query::JoinType::Inner,\n    query::QuerySource::Table(\"dx_team_hierarchies\".to_string(), Some(\"h\".to_string())),\n    query::QueryConditions::on().with_condition(expr!(\"t.id = h.ancestor_id\")),\n));\n\n// to a user with `user_name`\nlet github_authors_and_teams = github_authors_and_teams\n    .with_join(query::JoinQuery::new(\n        query::JoinType::Inner,\n        query::QuerySource::Table(\"dx_users\".to_string(), Some(\"dxu\".to_string())),\n        query::QueryConditions::on().with_condition(expr!(\"h.descendant_id = dxu.team_id\")),\n    ))\n    .with_field(\"user_name\".to_string(), expr!(\"dxu.name\"))\n    .with_field(\"github_username\".to_string(), expr!(\"dxu.source_id\"));\n```\n\n(Full example: \u003chttps://github.com/romaninsh/vantage/blob/main/bakery_model/examples/3-query-builder.rs\u003e)\n\nSQL is not the only query type supported by Vantage. You can use NoSQL queries, REST API\nand GraphQL queries too. Each query type is unique but will implement some shared traits.\n\n## Data Sets\n\nThe third concept introduced by Vantage is Data Sets. This allows you to create a generic\ninterface between your entities and query builder. This way you just need to define\nwhat you want to do with your entities, and the query will be built for you automatically:\n\n```rust\nlet clients: Table\u003cPostgres, Client\u003e = Client::table().with_condition(Client::is_paying_client().eq(\u0026true));\nlet unpaid_invoices: Table\u003cPostgres, Invoice\u003e = clients.ref_invoices()\n    .with_condition(Invoice::table().is_paid().eq(\u0026false));\n\nsend_email_reminder(unpaid_invoices, \"Pay now!\").await?.unwrap();\n\n// This can also use generics:\nasync fn send_email_reminder(data: impl ReadableDataSet\u003cInvoice\u003e, message: \u0026str) -\u003e Result\u003c(), Error\u003e {\n    for invoice in data.get().await? {\n        println!(\"Sending email to {} with message: {}\", invoice.client_name, message);\n    }\n}\n```\n\nLet me walk you through the code above so we can trace how Vantage builds queries out of\nentities for you:\n\n- Client::table() returns Table\u003cPostgres, Client\u003e type, because that's where our clients\n  are stored.\n- with_condition() narrows down the set of clients to only those who are paying. Because\n  Postgres supports `where` clause, this will become part of a `clients` table query.\n- ref_invoices() returns Table\u003cPostgres, Invoice\u003e, which will be based on Invoice::table()\n  but with additional conditions and client subquery.\n- final with_condition() narrows down the set of invoices to only those that are unpaid.\n\nResulting type is `sql::Table\u003cPostgres, Invoice\u003e`. It has been mutated to accomodate all the\nchanges we made to it, but query was not executed yet.\n\nNext we pass `unpaid_invoices` to `send_email_reminder` function, which would have accepted\nanything that implements `ReadableDataSet\u003cInvoice\u003e`. To `send_email_reminder` it does not\nmatter if the data is coming from SQL, NoSQL or REST API. It only intends to fetch the\ndata at some point.\n\n## Extensions and Plugins\n\n`Table\u003cD, E\u003e` implements a number of other useful traits:\n\n- `TableWithColumns` - allows you to describe table columns and map them to Rust types.\n- `TableWithConditions` - allows you to add conditions to the query.\n- `TableWithJoins` - allows you use 1-to-1 joins and store record data across multiple tables.\n- `TableWithQueries` - allow you to build additional queries like sum() or count().\n\nAnd of course you can add your own extensions to your table definitions:\n\n```rust\nimpl MyTableWithACL for Table\u003c_, MyEntity\u003e {}\n```\n\n## Vantage and stateful applications\n\nMany Rust application are stateful. Implementing a UI may include search field, filters,\npagination to limit amount of records you need to display for the user. There\nmay be a custom field selection and even custom column types that you would need to deal\nwith. Multiply that by 20-50 unique business entities, add all the UI you must build\nalong with ACL and validation rules.\n\nWithout generic UI components, this will be a nightmare to implement. Vantage can help\nyet again.\n\n`Table` can be kept in memory, shared through a Mutex or Signal, modified by various\nUI components and provide `Query` to different parts of your application. For instance,\nyour paginator component will want to use `table.count()` to determine how many records\nare there in total and use `table.set_limit()` to paginate resulting query. Your filter\nform component would use `table.get_columns()` to determine\nwhat fields are available for filtering and `table.add_condition()` to apply those\nconditions. Your data grid component would use `table.get()` to fetch the data.\n\nRich data grid views are the core component of business applications and while Vantage\ndoes not provide a UI, it can drive your generic components and provide both structure\nand data for them.\n\n## Quick Start\n\nWhile not mandatory, I recommend you to define some entities before starting with Rust.\nProvided [bakery_model](bakery_model/src/) implements entities for \"Baker\", \"Client\", \"Product\",\n\"Order\" and \"LineItem\" - specifying fields and relationships, you may write business code\nrelying on auto-complete and Rust type system:\n\n```rust\nuse vantage::prelude::*;\nuse bakery_model::*;\n\nlet set_of_clients = Client::table();   // Table\u003cPostgres, Client\u003e\n\nlet condition = set_of_clients.is_paying_client().eq(\u0026true);  // condition: Condition\nlet paying_clients = set_of_clients.with_condition(condition);  // Table\u003cPostgres, Client\u003e\n\nlet orders = paying_clients.ref_orders();   // orders: Table\u003cPostgres, Order\u003e\n\nfor row in orders.get().await? {  // Order\n    println!(\n        \"Ord #{} for client {} (id: {}) total: ${:.2}\\n\",\n        order.id,\n        order.client_name,\n        order.client_id,\n        order.total as f64 / 100.0\n    );\n};\n```\n\nOutput:\n\n```\nOrd #1 for client Marty McFly (id: 1) total: $8.93\nOrd #2 for client Doc Brown (id: 2) total: $2.20\nOrd #3 for client Doc Brown (id: 2) total: $9.95\n```\n\nSQL generated by Vantage and executed:\n\n```sql\nSELECT id,\n    (SELECT name FROM client WHERE client.id = ord.client_id) AS client_name,\n    (SELECT SUM((SELECT price FROM product WHERE id = product_id) * quantity)\n    FROM order_line WHERE order_line.order_id = ord.id) AS total\nFROM ord\nWHERE client_id IN (SELECT id FROM client WHERE is_paying_client = true)\n  AND is_deleted = false;\n```\n\nThis illustrates how Vantage combined specific rules of your code such as \"only paying clients\" with\nthe rules defined in the [bakery_model](bakery_model/src/), like \"soft-delete enabled for Orders\"\nand \"prices are actually stored in product table\" and \"order has multiple line items\" to generate\na single and efficient SQL query.\n\n## Using Vantage with Axum\n\nVantage fits well into Axum helping you build API handlers:\n\n```rust\nasync fn list_orders(\n    client: axum::extract::Query\u003cOrderRequest\u003e,\n    pager: axum::extract::Query\u003cPagination\u003e,\n) -\u003e impl IntoResponse {\n    let orders = Client::table()\n        .with_id(client.client_id.into())\n        .ref_orders();\n\n    let mut query = orders.query();\n\n    // Tweak the query to include pagination\n    query.add_limit(Some(pager.per_page));\n    if pager.page \u003e 0 {\n        query.add_skip(Some(pager.per_page * pager.page));\n    }\n\n    // Actual query happens here!\n    Json(query.get().await.unwrap())\n}\n```\n\nAPI response for `GET /orders?client_id=2\u0026page=1`\n\n```json\n[\n  { \"client_id\": 2, \"client_name\": \"Doc Brown\", \"id\": 2, \"total\": 220 },\n  { \"client_id\": 2, \"client_name\": \"Doc Brown\", \"id\": 3, \"total\": 995 }\n]\n```\n\nCompare to [SQLx](https://github.com/launchbadge/realworld-axum-sqlx/blob/main/src/http/articles/listing.rs#L79), which is more readable?\n\n## Key Features\n\n- 🦀 **Rust-first Design** - Leverages Rust's type system for your business entities\n- 🥰 **Complexity Abstraction** - Hide complexity away from your business logic\n- 🚀 **High Performance** - Generates optimal SQL queries\n- 🔧 **Zero Boilerplate** - No code generation or macro magic required\n- 🧪 **Testing Ready** - First-class support for mocking and unit-testing\n- 🔄 **Relationship Handling** - Elegant handling of table relationships and joins\n- 📦 **Extensible** - Easy to add custom functionality and non-SQL support\n\n## Roadmap to 1.0\n\nVantage needs a bit more work. Large number of features is already implemented, but\nsome notable features are still missing:\n\n- Vantage need Real-World app implementation for a backend as a test-case. I had some\n  issues with UUID fields, there could have been some other issues too.\n- Vantage works with PostgreSQL but not with GraphQL. I'll need to implement them both\n  to make Vantage more usable for the frontend applications.\n- Implement better ways to manipulate conditions, fields etc. If we can add those\n  dynamically, we should also be able to remove them too.\n- Vantage supports only base types (subtypes of serde_json::Value). I'll need to implement\n  additional DataSource-specific columns.\n- We need DataSource support for a regular REST APIs and implement example of Vantage\n  used as WASM interface between React components and the backend server.\n- I'd like to create example for Sycamore and open-source Tailwind components, showing\n  how multiple independent components can interact through signals and manipulate\n  a dataset collectively, fetching data when needed.\n- An example for Egui would also be nice.\n- I have Associated queries already implemented, but I also want to have associated entities,\n  which can have their types manipulated, validated and saved back. Associated entity should\n  work with dynamic forms.\n\n## Installation\n\nJust type: `cargo add vantage`\n\nIf you like what you see so far - reach out to me on BlueSky: [nearly.guru](https://bsky.app/profile/nearly.guru)\n\n# Walkthrough\n\n(You can run this [example](bakery_model/examples/0-intro.rs) with `cargo run --example 0-intro`)\n\nVantage interract with your data through a unique concept called \"Data Sets\". Your application will\nwork with different sets suc has \"Set of Clients\", \"Set of Orders\" and \"Set of Products\" etc.\n\nIt's easier to explain with example. Your SQL table \"clients\" contains multiple client records. We\ndo not know if there are 10 or 9,100,000 rows in this table. We simply refer to them as \"set of\nclients\".\n\nVantage defines \"Set of Clients\" is a Rust type, such as `Table\u003cPostgres, Client\u003e`:\n\n```rust\nlet set_of_clients = Client::table();   // Table\u003cPostgres, Client\u003e\n```\n\nAny set can be iterated over, but fetching data is an async operation:\n\n```rust\nfor client in set_of_clients.get().await? {   // client: Client\n    println!(\"id: {}, client: {}\", client.id, client.name);\n}\n```\n\nIn a production applications you wouldn't be able to iterate over all the records like this,\nsimply because of the large number of records. Which is why we need to narrow down our\nset_of_clients by applying a condition:\n\n```rust\nlet condition = set_of_clients.is_paying_client().eq(\u0026true);  // condition: Condition\nlet paying_clients = set_of_clients.with_condition(condition);  // paying_clients: Table\u003cPostgres, Client\u003e\n```\n\nIf our DataSource supports record counting (and SQL does), we can simply fetch through count():\n\n```rust\nprintln!(\n    \"Count of paying clients: {}\",\n    paying_clients.count().get_one_untyped().await?\n);\n```\n\nNow that you have some idea of what a DataSet is, lets look at how we can reference\nrelated sets. Traditionally we could say \"one client has many orders\". In Vantage we say\n\"clients set refers to orders set\":\n\n```rust\nlet orders = paying_clients.ref_orders();   // orders: Table\u003cPostgres, Order\u003e\n```\n\nType is automatically inferred, I do not need to specify it. This allows me to define\na custom method on Table\u003cPostgres, Order\u003e inside `bakery_model` and use it anywhere:\n\n```rust\nlet report = orders.generate_report().await?;\nprintln!(\"Report:\\n{}\", report);\n```\n\nImportantly - my implementation for `generate_report` comes with a unit-test. Postgres\nis too slow for unit-tests, so I use a mock data source. This allows me to significantly\nspeed up my business logic test-suite.\n\nOne thing that sets Vantage apart from other ORMs is that we are super-clever at building\nqueries. `bakery_model` uses a default entity type Order but I can supply another struct type:\n\n```rust\n#[derive(Clone, Debug, Serialize, Deserialize, Default)]\nstruct MiniOrder {\n    id: i64,\n    client_id: i64,\n}\nimpl Entity for MiniOrder {}\n```\n\n`impl Entity` is needed to load and store \"MiniOrder\" in any Vantage Data Set. Next I'll use\n`get_some_as` which gets just a single record from set. The scary-looking method\n`get_select_query_for_struct` is just to grab and display the query to you:\n\n```rust\nlet Some(mini_order) = orders.get_some_as::\u003cMiniOrder\u003e().await? else {\n    panic!(\"No order found\");\n};\nprintln!(\"data = {:?}\", \u0026mini_order);\nprintln!(\n    \"MiniOrder query: {}\",\n    orders\n        .get_select_query_for_struct(MiniOrder::default())\n        .preview()\n);\n```\n\nVantage adjusts query based on fields defined in your struct. My `MegaOrder` will remove `client_id` and\nadd `order_total` and `client_name` instead:\n\n```rust\n#[derive(Clone, Debug, Serialize, Deserialize, Default)]\nstruct MegaOrder {\n    id: i64,\n    client_name: String,\n    total: i64,\n}\nimpl Entity for MegaOrder {}\n\nlet Some(mini_order) = orders.get_some_as::\u003cMegaOrder\u003e().await? else {\n    panic!(\"No order found\");\n};\nprintln!(\"data = {:?}\", \u0026mini_order);\nprintln!(\n    \"MegaOrder query: {}\",\n    orders\n        .get_select_query_for_struct(MegaOrder::default())\n        .preview()\n);\n```\n\nIf you haven't already, now is a good time to run this code. Clone this repository and run:\n\n```bash\n$ cargo run --example 0-intro\n```\n\nAt the end, example will print out both queries. Lets dive into them:\n\n```sql\nSELECT id, client_id\nFROM ord\nWHERE client_id IN (SELECT id FROM client WHERE is_paying_client = true)\n  AND is_deleted = false;\n```\n\n`MiniOrder` only needed two fields, so only two fields were queried.\n\nCondition on \"is_paying_client\" is something we implicitly defined when we referenced Orders from\n`paying_clients` Data Set. Wait. Why is `is_deleted` here?\n\nAs it turns out - our table definition is using extension `SoftDelete`. In the `src/order.rs`:\n\n```rust\ntable.with_extension(SoftDelete::new(\"is_deleted\"));\n```\n\nThis extension modifies all queries for the table and will mark records as deleted when you\nexecute table.delete().\n\nThe second query is even more interesting:\n\n```sql\nSELECT id,\n    (SELECT name FROM client WHERE client.id = ord.client_id) AS client_name,\n    (SELECT SUM((SELECT price FROM product WHERE id = product_id) * quantity)\n    FROM order_line WHERE order_line.order_id = ord.id) AS total\nFROM ord\nWHERE client_id IN (SELECT id FROM client WHERE is_paying_client = true)\n  AND is_deleted = false;\n```\n\nAs it turns out - there is no physical field for `client_name`. Instead Vantage sub-queries\n`client` table to get the name. The implementation is, once again, inside `src/order.rs` file:\n\n```rust\ntable\n  .with_one(\"client\", \"client_id\", || Box::new(Client::table()))\n  .with_imported_fields(\"client\", \u0026[\"name\"])\n```\n\nThe final field - `total` is even more interesting - it gathers information from\n`order_line` that holds quantities and `product` that holds prices.\n\nWas there a chunk of SQL hidden somewhere? NO, It's all Vantage's query building magic. Look inside\n`src/order.rs` to see how it is implemented:\n\n```rust\ntable\n  .with_many(\"line_items\", \"order_id\", || Box::new(LineItem::table()))\n  .with_expression(\"total\", |t| {\n    let item = t.sub_line_items();\n    item.sum(item.total()).render_chunk()\n  })\n```\n\nWhere is multiplication? Apparently item.total() is responsible for that, we can see that in\n`src/lineitem.rs`.\n\n```rust\ntable\n  .with_one(\"product\", \"product_id\", || Box::new(Product::table()))\n  .with_expression(\"total\", |t: \u0026Table\u003cPostgres, LineItem\u003e| {\n    t.price().render_chunk().mul(t.quantity())\n  })\n  .with_expression(\"price\", |t| {\n    let product = t.get_subquery_as::\u003cProduct\u003e(\"product\").unwrap();\n    product.field_query(product.price()).render_chunk()\n  })\n```\n\n### Conclusion\n\nWe have discovered that behind a developer-friendly and very Rust-intuitive Data Set\ninterface, Vantage offers some really powerful features and hides complexity.\n\nWhat does that mean to your developer team?\n\nYou would need to define business entities once, but the rest of your team/code can focus on the\nbusiness logic - like improving that `generate_report` method!\n\nMy example illustrated how Vantage provides separation of concerns and abstraction of complexity - two\nvery crucial concepts for business software developers.\n\nUse Vantage. No tradeoffs. Productive team! Happy days!\n\n### Components of Vantage\n\nTo understand Vantage in-depth, you would need to dissect and dig into its individual components:\n\n1. DataSet - like a Map, but Rows are stored remotely and only fetched when needed.\n2. Expressions - recursive template engine for building SQL.\n3. Query - a dynamic object representing a single SQL query.\n4. DataSources - an implementation trait for persistence layer. Can be Postgres, a mock (more implementations coming soon).\n5. Table - DataSet with consistent columns, condition, joins and other features of SQL table.\n6. Field - representing columns or arbitrary expressions in a Table.\n7. Busines Entity - a record for a specific DataSet (or Table), such as Product, Order or Client.\n8. CRUD operations - insert, update and delete records in DataSet through hydration.\n9. Reference - ability for DataSet to return related DataSet (get client emails with active orders for unavailable stock items)\n10. Joins - combining two Tables into a single Table without hydration.\n11. Associated expression - Expression for specific DataSource created by operation on DataSet (sum of all unpaid invoices)\n12. Subqueries - Field for a Table represented through Associated expression on a Referenced DataSet.\n13. Aggregation - Creating new table from subqueries over some other DataSet.\n14. Associated record - Business Entity for a specific DataSet, that can be modified and saved back.\n\nA deep-dive into all of those concepts and why they are important for business software developers\ncan be found in the [Vantage Book](https://romaninsh.github.io/vantage).\n\n## Current status\n\nVantage currently is in development. See [TODO](TODO.md) for the current status.\n\n## Author\n\nVantage is implemented by **Romans Malinovskis**. To get in touch:\n\n- \u003chttps://www.linkedin.com/in/romansmalinovskis\u003e\n- \u003chttps://bsky.app/profile/nearly.guru\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromaninsh%2Fvantage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fromaninsh%2Fvantage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromaninsh%2Fvantage/lists"}