Skip to content

Data Elements

Data elements are a Roli base class used to store, use, and share structured data. Data elements are like spreadsheets where the columns are TypeScript properties and each row is a unique object instance. Data elements are great for organizing an application’s data model because the service and client modules have the same concrete types.

Versioned

Data elements are transparently versioned. Each time they’re saved to the database, their version changes. This version is used to ensure clients and services only work on the latest version of the Data element.

Properties

Data elements can contain any number of TypeScript properties with any name. You can create new properties in any Data method, not just the constructor, just like any other POJO.

Data Types

Most built-in JavaScript/TypeScript property types are supported including:

  • undefined
  • null
  • number
  • boolean
  • object
  • Map
  • Set
  • Array
  • Date
  • BigInt
  • types that extend Data
  • types that extend Endpoint
  • types that extend Session

Property validation

Data elements are ideal for holding business data because you can write property validation logic (getters/setters, methods) that ensure property values are valid before they’re changed or returned.

Local Execution

Data element methods are called inside the same process as the callee.

  • If the call is made from client code: the Data method is called inside the client.
  • If the call is made from service code: the Data method is called inside the service.

This is the opposite of Callables which route the call from the callee to the backend.

Creating a new Data element

To create a Data element you write a POJO that extends the Data base type in service code. The Data type is imported from the provided roli-runtime library.

// Service code
import {Data} from 'roli-runtime';
export class User extends Data {
constructor(public username: string) {
super(username);
}
}

The Data element constructor can contain any number of arguments. However, a key must be passed to super that uniquely identifies the Data element instance.

PrimaryKey

All Data elements have an immutable primaryKey property that’s set in the constructor by passing a string to super.

Instantiating

You create new Data object instances using the new operator. This works client-side as well as within endpoints.

// Service code OR Client code
const user = new User('Scott');

Retrieving Data Client-side

Data instances can be retrieved client-side using the getData RoliClient method. Pass the Data-derived type as the first argument and the primaryKey as the second. This will load the object into local client memory where it can be displayed in a UI element or modified just like any other JavaScript object.

// Client code
import { createRoliClient, MyData} from "<my-service>-service";
const roli = createRoliClient();
const myData = await roli.getData(MyData, "my primary key");

Retrieving Existing Data from Service Code

Data instances can be retrieved while inside service-code with the getData function imported from the provided roli-runtime library.

// Service code
const user = getData(User, "a primary key");

The arguments are the same as the clients-side example above. This loads the object into Service memory for use during the service method call. You can make changes or use the object like any other JavaScript object and even return it to the client.

Manually Saved

Data element changes must be saved to be made permanent. This is a manual process that involves a single function call either client-side or from inside service code.

Saving Client-Side

Data can be saved to the database on the client using the saveData function.

// Client code
user.lastName = 'Jones';
await roli.saveData(user);

A promise is returned that resolves when and if the Data element’s changes are written to the database. Additionally, the locally cached copy of the data element will have its internal version updated to reflect the server-side change.

Saving from Inside Service Code

Data can be saved from inside service code using the saveData function.

// Service code
import {Endpoint, saveData} from 'roli-runtime';
export class MyApiEndpoint extends Endpoint {
addUser(username: string) {
const user = new User(username);
saveData(user);
}
}

Unsaved Data Protection

When Data elements are modified inside service code, the Data element must either be saved or reverted before the end of the Callable method transaction. Otherwise the transaction will fail and the client will get an UnsavedChanges exception.

Additionally, if a new Data element or one that has changes is passed to a Callable method, this will count as unsaved changes and will too throw an exception unless saveData/saveDataGraphs is used to save the data element.

Deletion

Data elements cannot be deleted directly from client code. However, Data elements can be deleted from inside service code using the deleteObject function.

// Service code
import {Endpoint, deleteObject} from 'roli-runtime'
export class MyApiEndpoint extends Endpoint {
//...
deleteExercise(primaryKey: string) {
const exercise = getData(Exercise, primaryKey);
if (exercise) {
this._exerciseIndex.delete(exercise.primaryKey);
deleteObject(exercise);
} else {
throw new Error('Failed to delete exercise because it does not exist');
}
}
}

Keeping Data Updated Automatically

Client-side instances of Data elements can be kept updated automatically when other clients or service code make changes.

This feature allows clients to get a Data element once and then rely on it staying up to date as other clients make changes over time. This is useful when building real-time app dashboards, or real-time chat application, or anytime you’d like to share real-time state between a number of computers.

Create a Data Update Subscription

Use the subscribeUpdates RoliClient method to subscribe to updates on one or more Data elements.

// Client code
import { createRoliClient, MyData} from "<my-service>-service";
const roli = createRoliClient();
const myData = await roli.getData(MyData, "my primary key");
await roli.subscribeUpdates(myData);

This command keeps the MyData objects pointed to by the myData variable updated.

Update Notification

After you’ve subscribed to updates for a Data element you can get client side notifications when updates are made. This is a great place to refresh your UI or otherwise make presentation-layer changes.

Attach a listener with the addUpdateListener function, passing it one or more Data elements to attach the listener to and a callback function you want called when updates happen.

// Client code
await roli.subscribeUpdates(myData);
roli.addUpdateListener(myData, (e: DataUpdatedEvent<MyData>) => {
if (e.deleted) {
console.log(`${e.target.primaryKey} deleted`);
} else {
console.log(`${e.target.primaryKey} updated`);
}
});

Now, anytime the MyData elements pointed to by the myData variable receives an update from service code, the callback is called.

The e.target property contains the Data element that was updated.

The e.deleted property indicates whether the MyData element was deleted or not.

Unsubscribing from Updates

You should unsubscribe from updates to Data elements when you no longer need to receive them. A good place to do this in a UI is a page’s tear-down logic.

Unsubscribe from updates to one or more Data elements using the unsubscribeUpdates function, passing it one or more Data elements you previously subscribed to

// Client code
await roli.unsubscribeUpdates(myData);

This automatically removes listeners added using addUpdateListeners.

Automatic Subscription Cleanup

Roli will automatically delete all your Data update subscriptions shortly after the RoliClient object is collected by the client’s Garbage Collector.