Storage Events
When dealing with storage operations, it's important to note that document changes can impact the entire application. That's why we provide the ability to manage storage events, enabling you to trigger events when documents are created, updated, or removed within your storage system.
Supported Event types
At present, we offer support for the following storage events:
| Event | Description |
|---|---|
on_created |
This event is triggered when a new document is created within the specified storage. |
on_saved |
This event is triggered when a new document is created or an existing document is updated within the specified storage. |
on_updated |
This event is triggered when an existing document is updated within the specified storage. |
on_removed |
This event is triggered when an existing document is marked as removed within the specified storage. |
Event Execution
Storage Events operate on a First In, First Out (FIFO) basis, although simultaneous execution by multiple nodes is possible. For instance, while Node A is processing Event 61, Node B might already be executing Event 62, due to Node A concurrently processing Event 60, which Node B is not. Given the variability in processing speeds across different nodes, the FIFO execution logic for these events cannot be guaranteed.
Within the blueprint function, you can manage the execution order using lock & wait blocks. However, this approach may decelerate other events that are executing in the same thread and block. To ensure a 100% guarantee of execution order, it is advisable to offload the intensive tasks of Storage Events and transfer them to a queue.
Event Payload
When a document is created or updated, we will furnish you with the most recent document data, along with a list of fields that have undergone modifications. The document you receive represents the current state of the document at the moment of the event.
Given the extremely rapid and high-performance nature of these events, they may occasionally be combined. To provide you with a clearer understanding of what might occur in such rare scenarios, we've included a JSON example below illustrating the functioning of Storage Events.
Document Created Flow
{
"username": "Joe",
"_meta": {
"guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
"expiry": -1,
"entity": 1,
"removed": 0,
"created": 1706396400,
"updated": 1706396400,
"ums": 1706396400803,
"cms": 1706396400362,
"update_hash": "60e27209fa93063b5605e41605c2722ed428cae0"
}
}{
"object": {
"username": "Joe",
"_meta": {
"guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
"expiry": -1,
"entity": 1,
"removed": 0,
"created": 1706396400,
"updated": 1706396400,
"ums": 1706396400803,
"cms": 1706396400362,
"update_hash": "60e27209fa93063b5605e41605c2722ed428cae0"
}
},
"revisions": [
{
"key": "username",
"value": "Joe",
"previous_value": null
}
],
"fields_modified": [
"username"
]
}Document Updated Flow
{
"username": "Joe Do",
"firstname": "Joe",
"lastname": "Do",
"_meta": {
"guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
"expiry": -1,
"entity": 1,
"removed": 0,
"created": 1706396400,
"updated": 1706482800,
"ums": 1706482800417,
"cms": 1706396400362,
"update_hash": "8fc1d0b6a4e37225b1904c7de0f5a8c6431bb27e"
}
}{
"object": {
"username": "Joe Do",
"firstname": "Joe",
"lastname": "Do",
"_meta": {
"guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
"expiry": -1,
"entity": 1,
"removed": 0,
"created": 1706396400,
"updated": 1706482800,
"ums": 1706482800417,
"cms": 1706396400362,
"update_hash": "8fc1d0b6a4e37225b1904c7de0f5a8c6431bb27e"
}
},
"revisions": [
{
"key": "username",
"value": "Joe Do",
"previous_value": "Joe"
},
{
"key": "firstname",
"value": "Joe",
"previous_value": null
},
{
"key": "lastname",
"value": "Do",
"previous_value": null
}
],
"fields_modified": [
"username",
"firstname",
"lastname"
]
}Document Fast Updated Flow
It's worth noting that when updates occur at high speed, the triggered events may be consolidated. Below is an example of such a combined event. If a key has been modified multiple times, it will appear multiple times in the revisions array.
{
"username": "Joe",
"firstname": "Joe",
"_meta": {
"guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
"expiry": -1,
"entity": 1,
"removed": 0,
"created": 1706396400,
"updated": 1706482800,
"ums": 1706482800417,
"cms": 1706396400362,
"update_hash": "8fc1d0b6a4e37225b1904c7de0f5a8c6431bb27e"
}
}{
"username": "Joe",
"lastname": "Do",
"_meta": {
"guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
"expiry": -1,
"entity": 1,
"removed": 0,
"created": 1706396400,
"updated": 1706482800,
"ums": 1706482800419,
"cms": 1706396400362,
"update_hash": "b7440ac2915de6f038c1a55eb9376d024f8ce1a3"
}
}{
"object": {
"username": "Joe",
"firstname": "Joe",
"lastname": "Do",
"_meta": {
"guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
"expiry": -1,
"entity": 1,
"removed": 0,
"created": 1706396400,
"updated": 1706482800,
"ums": 1706482800419,
"cms": 1706396400362,
"update_hash": "b7440ac2915de6f038c1a55eb9376d024f8ce1a3"
}
},
"revisions": [
{
"key": "firstname",
"value": "Joe",
"previous_value": null
},
{
"key": "lastname",
"value": "Do",
"previous_value": null
}
],
"fields_modified": [
"firstname",
"lastname"
]
}Write Loops
A flow triggered by a storage event can itself write documents, and those writes raise storage events of their own. This composes across blueprints: if the on_saved flow for storage A updates a document in storage B, and the on_saved flow for storage B updates a document in storage A, the two flows feed each other forever. Each blueprint is locally correct, and because the studio shows one blueprint at a time, the cycle never appears on any canvas: it only exists in the composition. The same applies to a single flow that re-saves a document in its own storage.
Guard your writes
A store-write block inside a storage event flow should do one of two things:
- Skip the events it would raise. Pass
skip_on_savedin the options offunction_update_documentorfunction_create_document. The write happens, no storage events fire for it, and the chain ends there. This is the right default for derived or mirrored data. - Cascade on purpose, with a termination condition. If downstream flows must react to the write, add a data condition that makes the second pass a no-op. For example, compare the value before writing and only write when it actually changes. Without such a condition, the cascade is an infinite loop by construction.
Loop cancellation
The platform cancels runaway chains, so a loop starves within one generation instead of running forever. Two independent checks apply:
- By identity. Every storage event carries its chain's lineage: which storage and document raised each earlier hop. A flow may re-enter its own document once; that is what the termination-condition pattern relies on. The second time the same document on the same storage re-enters its own chain, the event is cancelled with
BLUEPRINT_LOOP. A two-storage cycle dies after about 5 hops, a self-loop after about 3. - By depth. Independently of identity, any chain deeper than 10 storage-event hops is cancelled the same way. This backstop covers loops the identity check cannot see, such as flows that create a new document each generation.
A cancelled event runs no flows, so it raises no further writes and the chain produces no next generation.
Reading the log
| Log line | Meaning |
|---|---|
| Informational line naming a suspected cycle | Your flow re-entered a document it already wrote in this chain. It still ran, because one revisit is allowed, but a second re-entry will be cancelled. Treat it as an early warning: check whether the cascade is intentional and has a termination condition. |
BLUEPRINT_LOOP warning |
The platform cancelled a runaway chain. The line names the storages and documents in the cycle; that is where to find the blueprints to fix. |
The cancellation is a safety net, not a design tool
A loop that gets cancelled has still run several generations of unnecessary writes before it dies. Design storage event flows so the chain ends on its own: skip the events on derived writes, and give every deliberate cascade a termination condition. See also Common Pitfalls.
Frequently asked
Which storage events does RUAL support?
Four: on_created when a new document is created, on_updated when an existing document is updated, on_saved for either, and on_removed when a document is marked as removed. Each fires a flow through the storage_event block.
Do RUAL storage events run in order?
Not guaranteed. Events are processed first in, first out, but multiple nodes execute them simultaneously at different speeds, so strict FIFO order cannot be guaranteed. For a hard execution order, offload the intensive work to the queue.
What data does a RUAL storage event contain?
The most recent document data, a revisions array with the key, value and previous_value of each modified field, and a fields_modified list. When updates happen at high speed, events may be consolidated into one.
What does BLUEPRINT_LOOP mean in RUAL?
The platform cancelled a runaway storage-event chain: two storage event flows were writing to each other's storage, or a chain grew deeper than 10 storage-event hops. The log line names the storages and documents in the cycle. Fix the flows by passing skip_on_saved on the store write, or give a deliberate cascade a termination condition.
