An Ontology edit refers to the idea of creating, modifying, or deleting an object. Functions support returning Ontology edits for use in a Function-backed Action. These Functions are authored using the @OntologyEditFunction
decorator which provides special semantics to simplify your code.
These Functions also use the @Edits
decorator in order to provide Actions with provenance information, which Actions may use to enforce 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 13
import { OntologyEditFunction } from "@foundry/functions-api"; import { Employee, Objects } from "@foundry/ontology-api"; export class CaveatEditFunctions { @Edits(Employee) @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 } }
Actions may require provenance information to enforce its permissions. To provide Actions with this information, you can use the @Edits
decorator, specifying the object types for which your Function returns edits.
Consider the following when using the @Edits
decorator:
Functions perform static analysis of your code to automatically detect referenced object types, but this should be not be relied on, since static analysis may fail to properly detect a reference. We strongly recommend that you always use the @Edits
decorator to provide provenance information about referenced object types.
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 12 13
import { OntologyEditFunction } from "@foundry/functions-api"; import { Employee, Aircraft, Objects } from "@foundry/ontology-api"; export class MyOntologyEditFunction { @Edits(Aircraft, Employee) @OntologyEditFunction() public myFunction(): void { const x = Objects.search().aircraft().all()[0]; x.businessCapacity = 3; const y = Objects.search().employee().all()[0]; y.department = "HR"; } }