Ontology transactions is in the beta phase of development and may not be available on your enrollment. Functionality may change during active development. Contact Palantir Support to request access.
Ontology transactions provide an additional execution model for functions that edit objects in the Ontology. Unlike edits made via regular Ontology edit functions, transaction functions:
This page shows how to write transaction functions and documents unique properties. For more details about how edit functions work, refer to the overview page.
Transaction-based edit functions differ from regular edit functions in several important ways.
Within a transaction function, any Ontology data read will reflect all edits previously made in the transaction function (and all edits made in a calling transaction function within the same action execution). Such edits are staged only within the transaction and are not visible in queries made by other users or through functions outside the context of the transaction. This allows you to query the Ontology from within the transaction function using search requests and aggregations with all staged edits being reflected in the result of the query.
Regular Python and TypeScript V2 Ontology edit functions require returning a batch of Ontology edits as the return value of the function for those edits to be applied. In transaction functions, the Ontology edits are automatically staged in the transaction and will be applied to the Ontology at the end of the function execution when the action completes. This frees up the return value of the function to return other information to the caller.
For example, you can apply an action that executes a TypeScript V2 transaction function which then makes some edits and further calls an AIP Logic function. Queries made by the AIP Logic function will return the Ontology changes made in the TypeScript V2 function; any additional edits made by the AIP Logic function will be added to the same transaction without a need to return them as part of the Logic function. All staged edits will be applied automatically once the action completes.
All operations within the transaction function, including queries, function calls, and AIP Logic executions, run within the same transaction. The transaction is started by the action that executes the function and is committed (that is, the staged Ontology edits are applied to the Ontology) after the function completes successfully. If the function throws an error, no edits are applied and the transaction is rolled back before being retried by the action.
Transaction functions use a WriteableClient instead of the standard Client. The WriteableClient provides direct methods for creating, updating, and deleting objects without needing to construct an edit batch.
Transaction functions must explicitly declare the entities that will be edited using the Edits type exported from the @osdk/functions package. The first parameter must be a WriteableClient<T> where T is the union of all edit types the function will perform. The return value is no longer constrained to be an array of edits so you can return any value. The following example declares a function that will edit the Employee object type:
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13import { Employee } from "@ontology/sdk"; import { Edits } from "@osdk/functions"; import { WriteableClient } from "@osdk/functions/experimental"; type OntologyEdit = Edits.Object<Employee>; export default async function assignTicket( client: WriteableClient<OntologyEdit>, employeeId: string, ticketId: string ): Promise<void> { // ... }
Use the create method on the WriteableClient to create new objects. You must specify the object type and provide a value for the primary key, along with any other properties you want to initialize.
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22import { Employee } from "@ontology/sdk"; import { Edits } from "@osdk/functions"; import { WriteableClient } from "@osdk/functions/experimental"; type OntologyEdit = Edits.Object<Employee>; async function createEmployee( client: WriteableClient<OntologyEdit>, employeeId: string, firstName: string, lastName: string ): Promise<Integer> { await client.create(Employee, { employeeId: employeeId, firstName: firstName, lastName: lastName }); return employeeId; } export default createEmployee;
When you need to generate an ID and then use it immediately:
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23import { Ticket } from "@ontology/sdk"; import { Edits, Integer } from "@osdk/functions"; import { WriteableClient } from "@osdk/functions/experimental"; import { randomUUID } from "crypto"; type OntologyEdit = Edits.Object<Ticket>; async function createTicket( client: WriteableClient<OntologyEdit>, title: string ): Promise<string> { const ticketId = randomUUID(); await client.create(Ticket, { ticketId: ticketId, title: title, status: "open" }); return ticketId; } export default createTicket;
Use the update method on the WriteableClient to modify object properties:
Copied!1await client.update(employee, { lastName: newName });
You can also update an object by referencing its API name and primary key:
Copied!1await client.update({ $apiName: "Employee", $primaryKey: 23 }, { lastName: newName });
You can use the update method to modify interface properties of an object through an Ontology interface:
Copied!1await client.update(person, { firstName: newFirstName });
Note that an interface property that is implemented by the primary key property of the underlying object cannot be updated.
You can delete an object by calling the delete method on the WriteableClient:
Copied!1await client.delete(ticket);
Objects may also be deleted using a primary key instead of an instance:
Copied!1await client.delete({ $apiName: "Ticket", $primaryKey: 12 });
Use the link and unlink methods on the WriteableClient to add or remove many to many links between objects:
Copied!1 2 3 4 5// Assign a ticket to an employee await client.link(employee, "assignedTickets", ticket); // Unassign a ticket from an employee await client.unlink(employee, "assignedTickets", ticket);
You can also reference either side of the link with an API name and primary key:
Copied!1 2 3 4 5await client.link( { $apiName: "Employee", $primaryKey: 23 }, "assignedTickets", { $apiName: "Ticket", $primaryKey: 12 } );
To edit one to many links, edit the foreign key property using a create or update object edit.
One of the key advantages of transaction functions is the ability to read data that was just written in the same transaction. This is useful for implementing workflows that require immediate consistency.
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 34 35 36 37 38 39 40 41 42 43 44import { Employee, Ticket } from "@ontology/sdk"; import { Edits, Integer } from "@osdk/functions"; import { WriteableClient } from "@osdk/functions/experimental"; import { randomUUID } from "crypto"; type OntologyEdit = Edits.Object<Ticket> | Edits.Link<Employee, "assignedTickets">; async function assignTicketAndCheckWorkload( client: WriteableClient<OntologyEdit>, employeeId: Integer, title: string ): Promise<{ ticketId: string, totalAssignedTickets: number }> { const ticketId = randomUUID(); // Create the ticket await client.create(Ticket, { ticketId: ticketId, title: title, status: "open" }); // Assign the ticket to the employee await client.link( { $apiName: "Employee", $primaryKey: employeeId }, "assignedTickets", { $apiName: "Ticket", $primaryKey: ticketId } ); // Query the employee's total workload including the newly assigned ticket // This works because of the read-after-write guarantee const result = await client.aggregate(Ticket, (tickets) => tickets .where(ticket => ticket.assignedTo.employeeId.exactMatch(employeeId)) .where(ticket => ticket.status.exactMatch("open")) .count() ); return { ticketId: ticketId, totalAssignedTickets: result }; } export default assignTicketAndCheckWorkload;
When you execute other functions or queries inside a transaction function, those operations run within the same transaction. This means any reads to the ontology will reflect changes made to the transaction prior. This applies to:
Any edits made by nested function calls are also part of the same transaction and will be committed or rolled back together.
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 34 35import { Employee, Ticket } from "@ontology/sdk"; import { Edits, Integer } from "@osdk/functions"; import { WriteableClient } from "@osdk/functions/experimental"; type OntologyEdit = Edits.Object<Ticket> | Edits.Link<Employee, "assignedTickets">; async function bulkAssignTickets( client: WriteableClient<OntologyEdit>, employeeId: Integer, ticketIds: string[] ): Promise<Integer> { let assignedCount = 0; for (const ticketId of ticketIds) { // Each link operation is part of the same transaction await client.link( { $apiName: "Employee", $primaryKey: employeeId }, "assignedTickets", { $apiName: "Ticket", $primaryKey: ticketId } ); // Update ticket status await client.update( { $apiName: "Ticket", $primaryKey: ticketId }, { status: "assigned" } ); assignedCount++; } // All edits will be committed together when the function completes return assignedCount; } export default bulkAssignTickets;
Understanding when transactions start and commit is important for building reliable functions:
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 25import { Employee } from "@ontology/sdk"; import { Edits, Integer } from "@osdk/functions"; import { WriteableClient } from "@osdk/functions/experimental"; async function updateEmployeeWithValidation( client: WriteableClient<Edits.Object<Employee>>, employeeId: Integer, newSalary: number ): Promise<void> { // Validate input if (newSalary < 0) { // Transaction will be rolled back throw new Error("Salary cannot be negative"); } // Update the employee await client.update( { $apiName: "Employee", $primaryKey: employeeId }, { salary: newSalary } ); // If we reach here, the transaction will be committed } export default updateEmployeeWithValidation;