From 10fde1e07e01bf0f1da9fd3eca37142feaaed632 Mon Sep 17 00:00:00 2001 From: James Rodewig Date: Wed, 11 Dec 2024 14:30:24 -0500 Subject: [PATCH] Revert "[DOCS-3787] Remove FQL typer workarounds" (#14) This reverts commit 01995bdec2febea217b5bdc1acd6be183218ca9b. --- DotNetSampleApp/Controllers/Customers.cs | 42 ++++++++++---------- DotNetSampleApp/Controllers/Orders.cs | 12 +++--- DotNetSampleApp/Controllers/Products.cs | 34 ++++++++-------- DotNetSampleApp/Controllers/QuerySnippets.cs | 17 ++++---- DotNetSampleApp/Services/SeedService.cs | 10 ++--- README.md | 1 + schema/collections.fsl | 3 +- schema/functions.fsl | 6 +-- 8 files changed, 65 insertions(+), 60 deletions(-) diff --git a/DotNetSampleApp/Controllers/Customers.cs b/DotNetSampleApp/Controllers/Customers.cs index 122896d..fc5480d 100644 --- a/DotNetSampleApp/Controllers/Customers.cs +++ b/DotNetSampleApp/Controllers/Customers.cs @@ -6,7 +6,7 @@ namespace DotNetSampleApp.Controllers; /// -/// Customers controller +/// Customers controller /// /// Fauna Client [ApiController, @@ -30,10 +30,10 @@ public async Task GetCustomer([FromRoute] string customerId) // // Use projection to only return the fields you need. var query = Query.FQL($""" - let customer = Customer.byId({customerId})! + let customer: Any = Customer.byId({customerId})! {QuerySnippets.CustomerResponse()} """); - + // Connect to fauna using the client. The query method accepts an FQL query // as a parameter and a generic type parameter representing the return type. var res = await client.QueryAsync(query); @@ -52,16 +52,16 @@ public async Task CreateCustomer(CustomerRequest customer) { // Create a new Customer document with the provided fields. var query = Query.FQL($""" - let customer = Customer.create({customer}) + let customer: Any = Customer.create({customer}) {QuerySnippets.CustomerResponse()} """); - + // Connect to fauna using the client. The query method accepts an FQL query // as a parameter and a generic type parameter representing the return type. var res = await client.QueryAsync(query); return CreatedAtAction(nameof(GetCustomer), new { customerId = res.Data.Id }, res.Data); } - + /// /// Update Customer Details /// @@ -85,10 +85,10 @@ public async Task UpdateCustomer( // // Use projection to only return the fields you need. var query = Query.FQL($""" - let customer = Customer.byId({customerId})!.update({customer}) + let customer: Any = Customer.byId({customerId})!.update({customer}) {QuerySnippets.CustomerResponse()} """); - + // Connect to fauna using the client. The query method accepts an FQL query // as a parameter and a generic type parameter representing the return type. var res = await client.QueryAsync(query); @@ -116,23 +116,25 @@ public async Task GetOrdersByCustomer( // Make sure to URL encode the `afterToken` value to preserve these // characters in URLs. var query = !string.IsNullOrEmpty(afterToken) - + // Paginate with the after token if it's present. ? Query.FQL($"Set.paginate({afterToken})") - + // Define an FQL query to retrieve a page of orders for a given customer. // Get the Customer document by id, using the ! operator to assert that the document exists. // If the document does not exist, Fauna will throw a document_not_found error. We then // use the Order.byCustomer index to retrieve all orders for that customer and map over // the results to return only the fields we care about. : Query.FQL($$""" - let customer = Customer.byId({{customerId}})! + let customer: Any = Customer.byId({{customerId}})! Order.byCustomer(customer).pageSize({{pageSize}}).map((order) => { + let order: Any = order + // Return the order. {{QuerySnippets.OrderResponse()}} }) """); - + // Connect to fauna using the client. The query method accepts an FQL query // as a parameter and a generic type parameter representing the return type. var result = await client.QueryAsync>(query); @@ -154,12 +156,12 @@ public async Task CreateCart([FromRoute] string customerId) // Call our getOrCreateCart UDF to get the customer's cart. The function // definition can be found 'server/schema/functions.fsl'. var query = Query.FQL($""" - let order = getOrCreateCart({customerId}) - + let order: Any = getOrCreateCart({customerId}) + // Return the cart. {QuerySnippets.OrderResponse()} """); - + // Connect to fauna using the client. The query method accepts an FQL query // as a parameter and a generic type parameter representing the return type. var res = await client.QueryAsync(query); @@ -182,12 +184,12 @@ public async Task AddItemToCart([FromRoute] string customerId, Ad // definition can be found 'server/schema/functions.fsl'. var query = Query.FQL($""" let req = {item} - let order = createOrUpdateCartItem({customerId}, req.productName, req.quantity) + let order: Any = createOrUpdateCartItem({customerId}, req.productName, req.quantity) // Return the cart as an OrderResponse object. {QuerySnippets.OrderResponse()} """); - + // Connect to fauna using the client. The query method accepts an FQL query // as a parameter and a generic type parameter representing the return type. var res = await client.QueryAsync(query); @@ -208,12 +210,12 @@ public async Task GetCart([FromRoute] string customerId) // Get the customer's cart by id, using the ! operator to assert that the document exists. // If the document does not exist, Fauna will throw a document_not_found error. var query = Query.FQL($""" - let order = Customer.byId({customerId})!.cart - + let order: Any = Customer.byId({customerId})!.cart + // Return the cart as an OrderResponse object. {QuerySnippets.OrderResponse()} """); - + // Connect to fauna using the client. The query method accepts an FQL query // as a parameter and a generic type parameter representing the return type. var res = await client.QueryAsync(query); diff --git a/DotNetSampleApp/Controllers/Orders.cs b/DotNetSampleApp/Controllers/Orders.cs index ab28892..9148fa4 100644 --- a/DotNetSampleApp/Controllers/Orders.cs +++ b/DotNetSampleApp/Controllers/Orders.cs @@ -26,10 +26,10 @@ public class Orders(Client client) : ControllerBase public async Task GetOrder([FromRoute] string id) { var query = Query.FQL($""" - let order = Order.byId({id})! + let order: Any = Order.byId({id})! {QuerySnippets.OrderResponse()} """); - + // Connect to fauna using the client. The query method accepts an FQL query // as a parameter and a generic type parameter representing the return type. var res = await client.QueryAsync(query); @@ -58,19 +58,19 @@ OrderRequest order // for validating that the order in a valid state to be processed and decrements the stock // of each product in the order. This ensures that the product stock is updated in the same transaction // as the order status. - var query = order.Status == "processing" + var query = order.Status == "processing" ? Query.FQL($""" let req = {order} - let order = checkout({id}, req.status, req.payment) + let order: Any = checkout({id}, req.status, req.payment) {QuerySnippets.OrderResponse()} """) - + // Define an FQL query to update the order. The query first retrieves the order by id // using the Order.byId function. If the order does not exist, Fauna will throw a document_not_found // error. We then use the validateOrderStatusTransition UDF to ensure that the order status transition // is valid. If the transition is not valid, the UDF will throw an abort error. : Query.FQL($$""" - let order = Order.byId({{id}})! + let order: Any = Order.byId({{id}})! let req = {{order}} // Validate the order status transition if a status is provided. diff --git a/DotNetSampleApp/Controllers/Products.cs b/DotNetSampleApp/Controllers/Products.cs index f663aad..ae0e74e 100644 --- a/DotNetSampleApp/Controllers/Products.cs +++ b/DotNetSampleApp/Controllers/Products.cs @@ -36,24 +36,24 @@ public async Task PaginateProducts( // fragment will either return all products sorted by category or all products in a specific // category depending on whether the category query parameter is provided. This will later // be embedded in a larger query. - var queryPrefix = string.IsNullOrEmpty(category) - ? Query.FQL($"Product.sortedByCategory().pageSize({pageSize})") + var queryPrefix = string.IsNullOrEmpty(category) + ? Query.FQL($"Product.sortedByCategory().pageSize({pageSize})") : Query.FQL($""" let category = Category.byName({category}).first() if (category == null) abort("Category does not exist.") - + Product.byCategory(category).pageSize({pageSize}) """); - + // The `afterToken` parameter contains a Fauna `after` cursor. // `after` cursors may contain special characters, such as `.` or `+`). // Make sure to URL encode the `afterToken` value to preserve these // characters in URLs. - var query = !string.IsNullOrEmpty(afterToken) - + var query = !string.IsNullOrEmpty(afterToken) + // Paginate with the after token if it's present. ? Query.FQL($"Set.paginate({afterToken})") - + // Define the main query. This query will return a page of products using the query fragment // defined above. : Query.FQL($$""" @@ -90,17 +90,17 @@ public async Task CreateProduct(ProductRequest product) if (category == null) abort("Category does not exist.") // Create the product with the given values. - let args = { + let args = { name: {{product.Name}}, price: {{product.Price}}, stock: {{product.Stock}}, description: {{product.Description}}, - category: category + category: category } - let product = Product.create(args) + let product: Any = Product.create(args) {{QuerySnippets.ProductResponse()}} """); - + // Connect to fauna using the client. The query method accepts an FQL query // as a parameter and a generic type parameter representing the return type. var result = await client.QueryAsync(query); @@ -125,7 +125,7 @@ public async Task UpdateProductById( var query = Query.FQL($$""" // Get the product by id, using the ! operator to assert that the product exists. // If it does not exist Fauna will throw a document_not_found error. - let product = Product.byId({{id}})! + let product: Any = Product.byId({{id}})! if (product == null) abort("Product does not exist.") // Get the category by name. We can use .first() here because we know that the category @@ -134,7 +134,7 @@ public async Task UpdateProductById( if (category == null) abort("Category does not exist.") // Update category if a new one was provided - let newCategory = Category.byName({{product.Category}})?.first() + let newCategory: Any = Category.byName({{product.Category}})?.first() let fields = { name: {{product.Name}}, @@ -154,7 +154,7 @@ public async Task UpdateProductById( {{QuerySnippets.ProductResponse()}} """); - + // Connect to fauna using the client. The query method accepts an FQL query // as a parameter and a generic type parameter representing the return type. var result = await client.QueryAsync(query); @@ -185,10 +185,10 @@ public async Task SearchProducts( // Make sure to URL encode the `afterToken` value to preserve these // characters in URLs. var query = !string.IsNullOrEmpty(afterToken) - + // Paginate with the after token if it's present. ? Query.FQL($"Set.paginate({afterToken})") - + // This is an example of a covered query. A covered query is a query where all fields // returned are indexed fields. In this case, we are querying the Product collection // for products with a price between minPrice and maxPrice. We are also limiting the @@ -204,7 +204,7 @@ public async Task SearchProducts( {{QuerySnippets.ProductResponse()}} }) """); - + // Connect to fauna using the client. The query method accepts an FQL query // as a parameter and a generic type parameter representing the return type. var result = await client.QueryAsync>(query); diff --git a/DotNetSampleApp/Controllers/QuerySnippets.cs b/DotNetSampleApp/Controllers/QuerySnippets.cs index 18ad1c2..ffd9b84 100644 --- a/DotNetSampleApp/Controllers/QuerySnippets.cs +++ b/DotNetSampleApp/Controllers/QuerySnippets.cs @@ -8,7 +8,7 @@ namespace DotNetSampleApp.Controllers; public static class QuerySnippets { /// - /// A Query snippet for customer response projection. + /// A Query snippet for customer response projection. /// /// public static Query CustomerResponse() @@ -25,11 +25,11 @@ public static Query CustomerResponse() } /// - /// A Query snippet for order response projection. + /// A Query snippet for order response projection. /// /// public static Query OrderResponse() - { + { return Query.FQL($$""" { id: order.id, @@ -63,7 +63,7 @@ public static Query OrderResponse() } /// - /// A Query snippet for product response projection. + /// A Query snippet for product response projection. /// /// public static Query ProductResponse() @@ -71,6 +71,7 @@ public static Query ProductResponse() return Query.FQL($$""" // Use projection to return only the necessary fields. let product: Any = product + let category: Any = product.category product { id: product.id, name: product.name, @@ -78,11 +79,11 @@ public static Query ProductResponse() description: product.description, stock: product.stock, category: { - id: product.category.id, - name: product.category.name, - description: product.category.description + id: category.id, + name: category.name, + description: category.description }, } """); } -} +} \ No newline at end of file diff --git a/DotNetSampleApp/Services/SeedService.cs b/DotNetSampleApp/Services/SeedService.cs index 44fffbb..eb55cda 100644 --- a/DotNetSampleApp/Services/SeedService.cs +++ b/DotNetSampleApp/Services/SeedService.cs @@ -45,7 +45,7 @@ public static void Init(Client client) {name: 'The Godfather II', price: 1299, description: 'A movie by Francis Ford Coppola', stock: 10, category: 'movies'}, {name: 'The Godfather III', price: 1299, description: 'A movie by Francis Ford Coppola', stock: 10, category: 'movies'} ].map(p => { - let existing = Product.byName(p.name).first() + let existing: Any = Product.byName(p.name).first() if (existing != null) { existing!.update({ stock: p.stock }) } else { @@ -85,16 +85,16 @@ public static void Init(Client client) client.QueryAsync(Query.FQL($$""" let customer = Customer.byEmail('fake@fauna.com').first()! let orders = ['cart', 'processing', 'shipped', 'delivered'].map(status => { - let order = Order.byCustomer(customer).firstWhere(o => o.status == status) + let order: Any = Order.byCustomer(customer).firstWhere(o => o.status == status) if (order == null) { - let newOrder = Order.create({ + let newOrder: Any = Order.create({ customer: customer, status: status, createdAt: Time.now(), payment: {} }) - let product = Product.byName('Drone').first()! - let orderItem = OrderItem.create({ order: newOrder, product: product, quantity: 1 }) + let product: Any = Product.byName('Drone').first()! + let orderItem: Any = OrderItem.create({ order: newOrder, product: product, quantity: 1 }) orderItem newOrder } else { diff --git a/README.md b/README.md index 98ae338..e5e1d5c 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,7 @@ Customer documents and related API responses: + // Use a computed field to calculate the customer's cumulative purchase total. + // The field sums purchase `total` values from the customer's linked Order documents. + compute totalPurchaseAmt: Number = (customer => customer.orders.fold(0, (sum, order) => { + + let order: Any = order + sum + order.total + })) ... diff --git a/schema/collections.fsl b/schema/collections.fsl index a0de39c..e64c5ca 100644 --- a/schema/collections.fsl +++ b/schema/collections.fsl @@ -72,8 +72,9 @@ collection Order { compute items: Set = (order => OrderItem.byOrder(order)) compute total: Number = (order => order.items.fold(0, (sum, orderItem) => { + let orderItem: Any = orderItem if (orderItem.product != null) { - sum + orderItem.product!.price * orderItem.quantity + sum + orderItem.product.price * orderItem.quantity } else { sum } diff --git a/schema/functions.fsl b/schema/functions.fsl index d34cc59..19896e1 100644 --- a/schema/functions.fsl +++ b/schema/functions.fsl @@ -70,7 +70,7 @@ function getOrCreateCart(id) { function checkout(orderId, status, payment) { // Find the order by id, using the ! operator to assert that the order exists. - let order = Order.byId(orderId)! + let order: Any = Order.byId(orderId)! // Check that we are setting the order to the processing status. If not, we should // not be calling this function. @@ -98,7 +98,7 @@ function checkout(orderId, status, payment) { // Check that the order items are still in stock. order!.items.forEach((item) => { - let product = item.product + let product: Any = item.product if (product.stock < item.quantity) { abort("One of the selected products does not have the requested quantity in stock.") } @@ -106,7 +106,7 @@ function checkout(orderId, status, payment) { // Decrement the stock of each product in the order. order!.items.forEach((item) => { - let product = item.product + let product: Any = item.product product.update({ stock: product.stock - item.quantity }) })