An Ontology edit refers to the idea of creating, modifying, or deleting an object. Functions supports returning Ontology edits for use in a Function-backed Action. These Functions are authored using the @OntologyEditFunction
decorator which provides special semantics to make writing Functions easier.
Functions provides out-of-the-box analysis of your code to detect the edited object types referenced in your code. These detected object types are used in providing Action provenance and may be used by the Actions service to enforce its permissions. You can examine these detected object types which are edited in the Functions helper in Code Repository. If an edited object type is referenced but not detected, you will have to manually specify the object type using the @Edits([object type])
decorator in order to enforce correct provenance for Actions permissions.
You can write unit tests for Ontology edit Functions using the APIs available for verifying Ontology edits.
The rest of this document describes how Ontology Edit Functions work behind the scenes to provide you with a better understanding of the underlying infrastructure.
A common misunderstanding about Ontology Edit Functions is whether or not running them will update objects in the Ontology. When you run an Ontology Edit Function in the Functions helper in Authoring, edits are not applied to the actual objects. The only way to update objects using a Function is by configuring an Action to use the Function as described in the documentation for Function-backed Actions.
This means that you can freely run Ontology Edit Functions in the Functions helper to validate results on various inputs, without concern that the objects themselves will be updated.
When an Ontology Edit Function is executed, all updates to objects are captured by the Functions infrastructure and returned at the end of the Function execution. This includes new object creations via the Objects.create() API, all property updates, and Object deletions.
Edits are collapsed intelligently so that the minimal set of edits are applied in an Action. For example, if you create a new object and then update its properties, a single Create Object edit will be returned containing the property updates. Similarly, updating multiple properties of an existing object will return a single Update Object edit containing all of the property edits. Deleting an object will erase any other property edits that were done before the deletion. The entire Function must succeed in order to generate the list of edits which is passed to the Actions service executing the atomic transaction.
The captured Ontology Edits are returned as a list from the Function execution. This is why Ontology Edit Functions must have a return type of void
or Promise<void>
: when they are executed, the true return type of the Function is a list of Ontology Edits, so it isn’t possible to simultaneously return another value.
Edits are captured in a single edit store over the entire lifecycle of a single Function execution. This means that it is possible to call into helper functions which create, update, or delete objects even if those helper functions aren’t published as Ontology Edit Functions. For example:
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
export class HelperEditFunctions { @Edits(ObjectA, ObjectB) @OntologyEditFunction() public createAndLink(): void { const objectA = this.createObjectA(); const objectB = this.createObjectB(); objectA.linkToB.set(objectB); } /** * Even though these helper functions aren't annotated with @OntologyEditFunction(), * they can create new objects for use in other edit functions. */ private createObjectA(): ObjectA { const objectA = Objects.create().objectA(this.generateRandomId()); objectA.prop1 = "example"; objectA.prop2 = 42; return objectA; } private createObjectB(): ObjectB { const objectB = Objects.create().objectB(this.generateRandomId()); objectB.prop1 = "another example"; return objectB; } /* Generate your primary keys as needed, for instance import { Uuid } from "@foundry/functions-utils"; private generateRandomId(){ return Uuid.random(); } */ }
When edits are done in a Function, the Functions infrastructure will return the edited values when you read them. For example, setting a property of an object and then retrieving it will return the new value:
airplane.departureTime = newDepartureTime;
console.log(airplane.departureTime); // Will log newDepartureTime
Deleting an object will remove it from search results and prevent accessing its properties.
Changes to objects and links are propagated to the Objects.search() APIs after your function has finished executing. This means that Objects.search() APIs will use the old objects, properties and links. As a result, search, filtering, search arounds, and aggregations may not reflect the edits to the Ontology, including creation and deletion. Your function will need to handle this case manually.
For the following example, assume there is an Employee with ID 1.
Copied!1 2 3 4 5 6 7 8 9 10 11 12
import { OntologyEditFunction } from "@foundry/functions-api"; import { Employee, Objects } from "@foundry/ontology-api"; export class CaveatEditFunctions { @OntologyEditFunction() public async editAndSearch(): Promise<void> { let employeeOne = Objects.search().employee().filter(e => e.id.exactMatch(1)).all()[0]; employeeOne.name = "Bob"; console.log(await Objects.search().employee().filter(e => e.name.exactMatch("Bob").count() ?? -1); // Expected: 1, Actual: 0 } }
Functions intelligently analyzes your code for the edited object types. This provenance may be used by other services such as Actions to enforce permissioning. It is important to specify the provenance correctly.
Rarely, the static analysis of your code may fail to detect the edited object types referenced (for example, if you cast an object type as any
). You then need to manually add the @Edits([object type])
decorator on top of your Functions specifying the object type.
For the following example, the two object types, Employee
and Aircraft
, are edited by the following function:
Copied!1 2 3 4 5 6 7 8 9 10 11
import { OntologyEditFunction } from "@foundry/functions-api"; import { Employee, Aircraft, Objects } from "@foundry/ontology-api"; export class ManuallySpecifyingObjectFunction { @OntologyEditFunction() public myFunction(): void { const x = Objects.search().aircraft().all()[0]; x.businessCapacity = 3; const y = Objects.search().employee().all()[0]; (y as any).department = ""// Expect: "Employee" and "Aircraft" object type to be detected, Actual: Only "aircraft" object type detected } }
You can see from the Functions helper in Code Repository that only one object type, Aircraft
, was detected. This is because the second object type, Employee
, was cast as any
and the typing was lost.
We can remedy this by manually specifying the object type using the decorator @Edits(Employee)
. Remember to import {Edits} from "@foundry/functions-api"
.
Copied!1 2 3 4 5 6 7 8 9 10 11 12
import { OntologyEditFunction, Edits } from "@foundry/functions-api"; // Import Edits import { Employee, Aircraft, Objects } from "@foundry/ontology-api"; export class ManuallySpecifyingObjectFunction { @Edits(Employee) // This decorator manually specifies that the object type "Employee" should be added to the edited object types provenance list. @OntologyEditFunction() public myFunction(): void { const x = Objects.search().aircraft().all()[0]; x.businessCapacity = 3; const y = Objects.search().employee().all()[0]; (y as any).department = "" } }
You can see from the Functions helper in Code Repository that all the edited object types are detected.