Data context reference
This document describes the structure of the Data object passed to Jyro scripts in Event Hooks and Dynamic Endpoints. Understanding this structure is essential for writing effective scripts.
Event Hooks
Event hooks receive contextual data about the domain operation being performed. The structure of the Data object varies based on the event type.
Create and Delete Events
For create and delete events, the entity is available directly under a property named after the entity type:
| Event Type | Data Property | Description |
|---|---|---|
BeforeOrganizationCreated | Data.organization | The organization being created |
AfterOrganizationCreated | Data.organization | The organization that was created |
BeforeOrganizationDeleted | Data.organization | The organization being deleted |
AfterOrganizationDeleted | Data.organization | The organization that was deleted |
BeforePersonCreated | Data.person | The person being created |
AfterPersonCreated | Data.person | The person that was created |
BeforePersonDeleted | Data.person | The person being deleted |
AfterPersonDeleted | Data.person | The person that was deleted |
Example - Create Event:
# BeforeOrganizationCreated or AfterOrganizationCreated
Log("Information", "Organization Name: " + Data.organization.Name)
Log("Information", "Organization ID: " + Data.organization.Id)
Log("Information", "Is Active: " + Data.organization.IsActive)
Example - Delete Event:
# BeforePersonDeleted
Log("Warning", "Deleting person: " + Data.person.FirstName + " " + Data.person.LastName)
Log("Warning", "Email: " + Data.person.EmailAddress)
Update Events
For update events, both the current (new) state and previous (old) state are provided:
| Event Type | Data Properties | Description |
|---|---|---|
BeforeOrganizationUpdated | Data.current, Data.previous | Current = new values, Previous = old values |
AfterOrganizationUpdated | Data.current, Data.previous | Current = saved values, Previous = old values |
BeforePersonUpdated | Data.current, Data.previous | Current = new values, Previous = old values |
AfterPersonUpdated | Data.current, Data.previous | Current = saved values, Previous = old values |
Important: For async events (e.g., AsyncOrganizationUpdated), Data.previous may be null as the previous state is not retained for performance reasons.
Example - Update Event:
# BeforeOrganizationUpdated or AfterOrganizationUpdated
Log("Information", "Organization updated: " + Data.current.Name)
# Compare old vs new values
if Data.previous != null then
if Data.current.Name != Data.previous.Name then
Log("Information", "Name changed from '" + Data.previous.Name + "' to '" + Data.current.Name + "'")
end
if Data.current.IsActive != Data.previous.IsActive then
Log("Warning", "IsActive changed from " + Data.previous.IsActive + " to " + Data.current.IsActive)
end
end
Entity Property Reference
Organization Properties
{
"Id": "guid-string",
"TenantId": "string",
"Name": "string",
"Description": "string or null",
"IsActive": true,
"ParentOrganizationId": "guid-string or null",
"FacetInstances": [
{
"FacetDefinitionId": "guid-string",
"Values": ["value1", "value2"]
}
],
"FieldInstances": [
{
"FieldDefinitionId": "guid-string",
"Items": [
{
"Value": "string",
"IsPrimary": true
}
]
}
],
"HybridListInstanceItems": [
{
"HybridListDefinitionId": "guid-string",
"CategoryId": "guid-string",
"ItemId": "guid-string or null",
"FreeTextValue": "string or null"
}
],
"EntitlementInstances": [],
"CreatedAt": "2024-01-15T10:30:00Z",
"UpdatedAt": "2024-01-15T10:30:00Z"
}
Person Properties
{
"Id": "guid-string",
"TenantId": "string",
"FirstName": "string",
"LastName": "string",
"MiddleName": "string or null",
"EmailAddress": "string",
"Suffix": "string or null",
"Prefix": "string or null",
"IsActive": true,
"FacetInstances": [
{
"FacetDefinitionId": "guid-string",
"Values": ["value1", "value2"]
}
],
"FieldInstances": [],
"HybridListInstanceItems": [],
"CreatedAt": "2024-01-15T10:30:00Z",
"UpdatedAt": "2024-01-15T10:30:00Z"
}
All Supported Event Types
| Category | Before Event | After Event | Async Event |
|---|---|---|---|
| Organization | BeforeOrganizationCreated | AfterOrganizationCreated | AsyncOrganizationCreated |
| Organization | BeforeOrganizationUpdated | AfterOrganizationUpdated | AsyncOrganizationUpdated |
| Organization | BeforeOrganizationDeleted | AfterOrganizationDeleted | AsyncOrganizationDeleted |
| Person | BeforePersonCreated | AfterPersonCreated | AsyncPersonCreated |
| Person | BeforePersonUpdated | AfterPersonUpdated | AsyncPersonUpdated |
| Person | BeforePersonDeleted | AfterPersonDeleted | AsyncPersonDeleted |
| FacetDefinition | BeforeFacetDefinitionCreated | AfterFacetDefinitionCreated | - |
| FacetDefinition | BeforeFacetDefinitionUpdated | AfterFacetDefinitionUpdated | - |
| FacetDefinition | BeforeFacetDefinitionDeleted | AfterFacetDefinitionDeleted | - |
| FieldDefinition | BeforeFieldDefinitionCreated | AfterFieldDefinitionCreated | - |
| FieldDefinition | BeforeFieldDefinitionUpdated | AfterFieldDefinitionUpdated | - |
| FieldDefinition | BeforeFieldDefinitionDeleted | AfterFieldDefinitionDeleted | - |
| Secret | BeforeSecretCreated | AfterSecretCreated | - |
| Secret | BeforeSecretUpdated | AfterSecretUpdated | - |
| Secret | BeforeSecretDeleted | AfterSecretDeleted | - |
Event Timing Semantics
| Timing | When Executed | Can Cancel | Can Modify | Database State |
|---|---|---|---|---|
| Before | Before database save | Yes (via CancelAction()) | Facets, Fields, HybridLists, Entitlements | Entity not yet persisted |
| After | After database save | No | Read-only (changes ignored) | Entity persisted |
| Async | Background (fire-and-forget) | No | Read-only | Entity persisted |
Dynamic Endpoints
Dynamic endpoints receive HTTP request context and can control the HTTP response.
Input: Data.request
The Data.request object contains all information about the incoming HTTP request:
{
"Request": {
"method": "GET",
"path": "/api/dynamic/v1/my-endpoint",
"queryString": "?param1=value1¶m2=value2",
"query": {
"param1": "value1",
"param2": "value2"
},
"headers": {
"authorization": "Bearer eyJ...",
"content-type": "application/json",
"host": "localhost:7000",
"user-agent": "Mozilla/5.0..."
},
"body": {
"key": "value"
},
"user": {
"isAuthenticated": true,
"name": "john@example.com",
"principalId": "user:john@example.com",
"roles": ["user", "admin"]
}
}
}
Request Properties Reference
| Property | Type | Description |
|---|---|---|
Data.request.method | string | HTTP method: GET, POST, PUT, PATCH, DELETE |
Data.request.path | string | Full request path including /api/dynamic/ prefix |
Data.request.queryString | string | Raw query string including ? (e.g., ?id=123&name=test) |
Data.request.query | object | Parsed query parameters as key-value pairs |
Data.request.headers | object | HTTP headers with lowercase keys |
Data.request.body | object/null | Parsed JSON body (for POST/PUT/PATCH requests) |
Data.request.user | object | Authenticated user information |
User Properties Reference
| Property | Type | Description |
|---|---|---|
Data.request.user.isAuthenticated | boolean | true if user is authenticated |
Data.request.user.name | string | User’s identity name (usually email) |
Data.request.user.principalId | string | Full principal ID (e.g., user:john@example.com) |
Data.request.user.roles | array | List of user’s assigned roles |
Output: Response Control
Control the HTTP response by setting these special properties on the Data object:
| Property | Type | Default | Description |
|---|---|---|---|
Data._payload | any | null | Response body (will be JSON serialized) |
Data._statusCode | number | 200 | HTTP status code |
Data._redirect | string | null | URL to redirect to (returns 302) |
Data._contentType | string | application/json | Response Content-Type header |
Example: Complete Dynamic Endpoint
# GET /api/dynamic/v1/organization-summary?orgId=xxx
# Validate required parameter
if Data.request.query.orgId == null then
Data._payload = { "error": "orgId parameter is required" }
Data._statusCode = 400
return
end
# Check authentication
if Data.request.user.isAuthenticated == false then
Data._payload = { "error": "Authentication required" }
Data._statusCode = 401
return
end
# Get organization data
var org = GetOrganizationById(Data.request.query.orgId)
if org == null then
Data._payload = { "error": "Organization not found" }
Data._statusCode = 404
return
end
# Get facet data
var facets = GetOrganizationFacetInstances(org.Id)
# Build response
Data._payload = {
"id": org.Id,
"name": org.Name,
"description": org.Description,
"isActive": org.IsActive,
"facetCount": facets.length,
"requestedBy": Data.request.user.name,
"requestedAt": Now()
}
Data._statusCode = 200
Example: POST with Body Validation
# POST /api/dynamic/v1/notifications/send
# Validate request body
if Data.request.body == null then
Data._payload = { "error": "Request body is required" }
Data._statusCode = 400
return
end
if Data.request.body.userId == null then
Data._payload = { "error": "userId is required" }
Data._statusCode = 400
return
end
if Data.request.body.message == null then
Data._payload = { "error": "message is required" }
Data._statusCode = 400
return
end
# Send notification
var success = SendNotification(
Data.request.body.userId,
Data.request.body.title or "Notification",
Data.request.body.message,
Data.request.body.type or "Info",
Data.request.body.link
)
if success then
Data._payload = { "success": true, "message": "Notification sent" }
Data._statusCode = 200
else
Data._payload = { "success": false, "message": "Failed to send notification" }
Data._statusCode = 500
end
Example: Redirect Response
# GET /api/dynamic/v1/redirect-example
# Set redirect URL (returns HTTP 302)
Data._redirect = "/dashboard"
# Note: _payload is ignored when _redirect is set
Example: Custom Content Type
# GET /api/dynamic/v1/plain-text
Data._payload = "Hello, this is plain text!"
Data._contentType = "text/plain"
Data._statusCode = 200
Best Practices
Event Hooks
- Always check for null before accessing nested properties
- Use Before hooks for validation and data enrichment
- Use After hooks for notifications and side effects
- Use CancelAction() only in Before hooks when validation fails
- Keep scripts simple - complex logic belongs in the domain layer
Dynamic Endpoints
- Validate all input - check query parameters and body data
- Check authentication when required (
Data.request.user.isAuthenticated) - Return appropriate status codes - 400 for bad input, 401 for auth, 404 for not found
- Always set _payload even for errors
- Log important operations with the
Log()function
Common Patterns
Null-safe property access:
# Safe way to access potentially null properties
var name = Data.organization.Name or "Unknown"
var desc = Data.organization.Description or ""
Check if update changed a specific field:
# In update events
if Data.previous != null and Data.current.Name != Data.previous.Name then
Log("Information", "Name was changed")
end
Build response objects:
# Dynamic endpoint response
Data._payload = {
"success": true,
"data": {
"id": result.Id,
"name": result.Name
},
"meta": {
"requestedBy": Data.request.user.name,
"timestamp": Now()
}
}
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
Data.organization is null | Wrong event type or entity not in context | Check event type matches entity |
Data.previous is null | Async event or create/delete event | Use sync events for previous state |
Data.request is null | Script is event hook, not dynamic endpoint | Check script context |
_payload not returned | Script error or early return | Check logs for errors |
| Changes to entity ignored | After hook or protected property | Use Before hook for modifications |
Debugging Tips
- Use
Log("Debug", ...)to inspect object values - Check application logs for script execution errors
- Verify the event type matches your expectations
- Test with simple scripts first, then add complexity