Fields

In RONIN, you define the structure of your data using schemas. They consist of fields, which define the format of your data. Fields can have different types, which decide the kind of values they can store.

In traditional (relational) databases, the respective equivalent to a schema would be a "table", and the equivalent to a field would be a "column". While RONIN is powered by a relational data layer (SQLite), data is exposed to clients in the form of objects (records) instead of lists of values (rows), which naturally makes the concept of tables and columns inapplicable.

Field Types

RONIN supports a range of different field types for storing various kinds of values. Below, you can find a complete overview.

Unless a default value is provided for a field, its value will be null by default. This value can also be set explicitly by the client if the field is not marked as "Required".

String

The value is stored as a UTF-8 string of text in SQLite (TEXT).

Boolean

The value is stored as a signed integer (1 for true, 0 for false) in SQLite (INTEGER).

Number

The value is stored as a 8-byte IEEE floating point number in SQLite (REAL).

If no decimal digits are provided when storing a value, a single decimal digit is set as 0 by default. Following the behavior of JavaScript, if all decimal digits in the stored value are zero, the value is returned to the client as an integer, otherwise as a floating point number.

Date

The value is stored as a 2-byte signed integer in SQLite (INTEGER), representing the number of milliseconds that have elapsed since the start of the Unix epoch.

Timezones

All date values are stored and returned using the UTC timezone. To convert a given date into a specific timezone, please perform the conversion on the client. This follows the increasingly common "multi-player" need of web applications, where multiple users from different timezones might consume the same data.

It is generally not advised to store any timezones alongside Date fields in your database, since users of web apps typically move freely across timezones, which means that the timezone in which the date is displayed must depend on the current location of the client and not be static.

If you explicitly need to store a timezone alongside a UTC date, you can use a String field to store the identifier of the timezone, and a Date field to store the date itself.

Blob

The value is stored in a separate S3-like blob storage, which allows any theoretical size for binary data.

On the client, blob values can be provided in any of the supported formats, such as Blob, File, ArrayBuffer, ReadableStream, or the Node.js primitive Buffer, which are all internally converted into a single stream of binary data that is stored on disk, without any metadata, in the blob storage.

Any potential file metadata is stored as a UTF-8 string of JSON text in SQLite (TEXT).

JSON

The value is stored as a UTF-8 string of text in SQLite (TEXT).

When the value is provided as a JavaScript object or array, it is automatically serialized to a JSON text string with no indentation, whitespace, and newlines (following the behavior of JSON.stringify({...}) with no additional arguments in JavaScript), before being stored in the database. When the value is retrieved from the database, it is automatically parsed from JSON to a JavaScript object.

The field type can only contain valid object or array notations and null. Primitive values that are normally supported by JSON, such as strings, numbers, or booleans are not allowed and must be stored using their respective dedicated field types in RONIN.

Reference

In order to establish a relation between a parent schema and a child schema, you can add fields of type "Reference" to the child schema, which RONIN will then use to store relations between the records of those schemas whenever records are created or updated.

For example, if your application requires a concept of "Teams" that contain "Members" you could create a schema called "Team" which would be the parent schema, and a schema called "Member" which would be the child schema. They are separate schemas and have different fields, but they relate to each other.

To establish a relation between both schemas (or rather between the records of both schemas), you can add a new field of type "Reference" to the "Member" schema, which could have an arbitrary name and slug. For the purpose of the aforementioned example, however, you could use "Team" as the field name, and "team" as the field slug, such that the field can be used to reference the parent team of every member.

Reference Actions

Once you've established relations between your records, it is essential for the stability and reliability of your application to avoid any inconsistency issues among those relations. Like this, you prevent your application from ending up in a state that it does not expect, which would cause breakage.

As an example, if a "Team" inside your application gets deleted, all the "Member" records that relate to it would now point to a non-existing record, which your application should not have to handle.

RONIN solves this by making it possible to define "Actions" for fields of type "Reference" which are executed whenever a given action is performed on the parent record. Namely, the following actions are available:

  • None: No actions will be performed. This option is not recommended unless your application prevents the aforementioned kinds of inconsistency issues itself.

  • Cascade: If the selected kind of action (such as "Delete" or "Update") is performed on the parent record, the action will be propagated down to all the child records that have referenced the parent record. For example, if "Cascade on Delete" is selected, and the parent record is deleted, all child records will be deleted as well. Similarly if "Cascade on Update" is selected, and the parent record is updated, the exact modification applied to the parent record will also be applied to the child records.

  • Clear: If the selected kind of action (such as "Delete" or "Update") is performed, the value of the "Reference" field in the child record will be cleared, meaning reset back to its original value of null. For example, if "Clear on Delete" is selected, and the parent record is deleted; the "Reference" field on the child records that established the relation will be cleared automatically.

  • Restrict: Prevents the selected kind of action (such as "Delete" or "Update") from being performed entirely. If the action is performed, the query performing the action will result in an error.

It is important to note that, at the moment, Reference Actions can only be set when a field is added and can no longer be modified after the field has been added. This is a limitation that will be removed in the future.

Default Fields

Every record contains several default fields that are managed automatically by RONIN.

ID

The most important default field generated by RONIN (whenever a new record is created) is the “ID” field, which has the slug id and contains an identifier of the record that is unique within your entire Space (across all Schemas).

For example, such an ID might look like this:

Plaintext
rec_vais0g9rrk995tz0

The ID always consists of the following parts:

  • The ID Prefix, consisting of 3 letters.

  • An underscore character (_) used as a separator.

  • A collision-proof unique identifier, consisting of 16 alpha-numeric characters.

Customizing IDs

In order to change the ID Prefix from rec (which is short for “record”) to any other letter sequence of your choice, you may do so using the “ID Prefix” field in the “Settings” section of your schema on the dashboard. For example, you might use the prefix acc for a schema named “Account”.

However, please note that changing this setting only applies to records created after you’ve updated the setting, so you may need to run a manual migration, or create a new schema with the right ID Prefix set from the beginning.

If this isn’t enough for you, you may also overwrite the id field entirely, by passing it when creating or updating a record:

TypeScript
await create.account.with({
  id: 'account_vais0g9rrk995tz0',
  name: 'Elaine',
  handle: 'elaine',
});

In favor of consistency (within the RONIN platform, but also among different services available on the web), we recommend sticking to the default format.

Metadata

Whenever a record is created or updated, RONIN automatically stores and updates a ronin field that contains meta-information about the record:

TypeScript
interface Metadata {
  createdAt: Date | string;
  updatedAt: Date | string;
  createdBy: `acc_${string}`;
  updatedBy: `acc_${string}`;
  locked: boolean;
}

Although this information is entirely read-only (and therefore cannot be overwritten), you may use it to filter or order records when querying:

TypeScript
await get.blogPosts.orderedBy.ascending(['ronin.createdAt']);
 
await get.blogPosts.with.ronin.createdAt.lessThan(new Date());
 
await get.blogPosts.with.ronin.locked(true);

Note that, even though they might look like record references, the ronin.createdBy and ronin.updatedBy fields are managed automatically and internally by RONIN, just like all the other field that are nested into ronin, which means you won’t be able to resolve them using the including query instruction.