Introduction to FSL

Fauna Schema Language (FSL) enables you to express your schema in a human readable form, using the Dashboard, which enables you to manage your schema and data model as code in a revision control system of your choice. By managing your schema as code, your data modeling and schema migrations can be done in accordance with the same DevOps principles you use to manage application code.

An FSL schema definition is a collection of schema item definitions. Each schema item is described by a name and properties, and can reference other schema items, including forward referencing. You define and manage your data model using FSL.

The FSL declarative language makes it possible for to define all your database elements as a set of files. Supported elements include:

E-commerce data model example

These are examples that might represent an e-commerce data model.

Customer collection

{
  id: "357949884874096724",
  coll: Customer,
  ts: Time("2023-02-28T23:21:01.810Z"),
  name: "Susan Nelson",
  address: {
    street: "5677 Strong St.",
    state: "CA",
    city: "San Rafael",
    country: "USA",
    zip: 97652
  }
}

Product collection

{
  id: "357949884859416660",
  coll: Product,
  ts: Time("2023-02-28T23:21:01.810Z"),
  name: "The Doors of Stone",
  type: "book",
  price: 3000,
  quantity: 0
}

Order collection

{
  id: "357949884877242452",
  coll: Order,
  ts: Time("2023-02-28T23:21:01.810Z"),
  customer: Customer.byId("357949884876193876"),
  product: Product.byId("357949884871999572"),
  date: "2003-03-03"
}

Index definition

An application requirement is to allow end-users to search for products by product category. You can define an index, which gives improved performance over the where() method, to facilitate that query pattern and give the index the name byCategory:

collection Product {
  indexes byCategory {
    terms: [ .category ]
    values: [ desc(.price), .name ]
  }
}

The "byCategory" index indexes on the category field as an index term and is ordered by descending price and ascending name. The index allows an application to efficiently query for products by product category, such as electronics, books, or hardware, and returns the result set in price and name order, listing the most expensive products by name first.

Unique constraint definition

FSL also allows you to constrain the possible values a given field can have to ensure that the document fields have correct and valid values according to your business logic.

The values can be a unique constraint. For example, the example e-commerce application requires that each product in the Product collection has a unique name. You can enforce that constraint with the following collection definition:

collection Product {
  name:     string
  category: string
  price:    int
  quantity: int

  indexes byCategory {
    terms:  [ .category ]
    values: [ desc(.price), .name ]
  }
  unique: [ .name ]
}

The unique keyword accepts an array of field names so you can express all of your unique constraints in a single statement.

Define a function

User-defined functions (UDFs) are conceptually similar to stored procedures and can encapsulate sophisticated transactions in manageable and maintainable units. Because UDFs execute in the Fauna service, they reduce the complexity of client-side applications.

This example adds a UDF to the e-commerce model to show how you define a UDF using FSL:

function getOrdersByCustomer( string: name ): Set<Order> {
  Order.all(.customer.name == name)
}

Notice that the Fauna function syntax is similar to typescript syntax. The function getOrderByCustomer takes a parameter name, which is a String type, and returns a Set of Order documents.

UDFs can have attributes that give them added utility. UDF attributes are defined using annotations. This example assigns a role to a function using the @role annotation so the function executes with the permissions associated with the ShopAdmin role:

@role(ShopAdmin)
{
  author: "A. Lovelace",
}
function getOrdersByCustomer( string: name ): Set<Order> {
  Order.all.where(.customer.name == name)
})

Define a role

When managing access to a database, Fauna users can use the out-of-the-box roles or define custom roles.

To offer the e-commerce application as a retail platform for third-party shops, you might want to define a new type of user, such as a shop administrator, and limit shop administrator access to the Customer, Product, and Order collections. To do this, add a shopAdmin field to some Customer documents. Documents that have a value of true for this field are ShopAdmin customers.

In defining the role, you can give ShopAdmin customers greater privileges, including adding or modifying their own products to the Product collection but being prohibited from modifying the products of shops they aren’t an administrator of:

role AmalgamatedWidgetAdmin {

  membership Customer {
    predicate (.shopAdmin == true) }

    privileges Product {
      read
      create {
        predicate (product => product.shopId == Query.identity()!.shopId)

      }

      write {
        predicate ((old, new) => {
          old.shopId == Query.identity()!.shopId && old.shopId == new.shopId
        })
      }
    }
  }
}

The membership property defines the restricted privileges shop administrators have. Only customers where the ShopAdmin field is true have membership in this role.

The write privilege takes the current and new state.

Shop administrators can read any product in the Product collection but may only create and write products in their own shop.

Is this article helpful? 

Tell Fauna how the article can be improved:
Visit Fauna's forums or email docs@fauna.com

Thank you for your feedback!