Action Type DSL
The Action Type DSL (Domain-Specific Language) is Cloudillo’s system for defining action types declaratively. Instead of hardcoding behavior for each action type, the DSL allows flexible configuration of validation rules, processing behavior, and lifecycle hooks.
Overview
The DSL system serves several purposes:
- Validation: Define what fields are required/optional for each action type
- Behavior Configuration: Control how actions are processed, delivered, and stored
- Extensibility: Add new action types without modifying core code
- Consistency: Ensure all actions of a type follow the same rules
ActionDefinition Structure
Each action type is defined by an ActionDefinition:
struct ActionDefinition {
action_type: String, // e.g., "POST", "CMNT", "FLLW"
version: u32, // Schema version
metadata: Metadata, // Display name, description, etc.
subtypes: Vec<String>, // e.g., POST:IMG, POST:VID
fields: Vec<FieldDef>, // Field definitions
schema: Schema, // JSON Schema for validation
behavior: Behavior, // Processing behavior flags
hooks: Hooks, // Lifecycle hooks
permissions: Permissions, // Permission requirements
key_pattern: String, // Database key pattern for overriding
}Metadata
struct Metadata {
display_name: String, // Human-readable name
description: String, // What this action represents
category: Category, // content, user, communication, metadata
}Fields
Each field in an action is defined with validation rules:
struct FieldDef {
name: String, // Field name in JWT (e.g., "c", "p", "vis")
field_type: FieldType, // string, object, array, number, boolean
required: bool, // Is this field required?
validate: Option<Schema>, // Additional validation rules
}Behavior Flags
Behavior flags control how actions are processed:
| Flag | Description | Example Types |
|---|---|---|
broadcast |
Send to multiple recipients (followers) | POST, REPOST |
allow_unknown |
Accept actions from unknown senders | CONN |
ephemeral |
Don’t persist to database | PRES (presence) |
approvable |
Can be approved/rejected by recipient | CONN, FLLW |
requires_subscription |
Sender must be subscribed | MSG, CONV |
deliver_subject |
Deliver to subject owner | CMNT, REACT |
deliver_to_subject_owner |
Alternative delivery to subject owner | CMNT |
Behavior Examples
POST Action:
Behavior {
broadcast: true, // Send to all followers
allow_unknown: false, // Must be from followed/connected user
ephemeral: false, // Persist to database
approvable: false, // No approval needed
requires_subscription: false, // No subscription required
deliver_subject: false, // Not a reply
}CONN Action:
Behavior {
broadcast: false, // Send only to audience
allow_unknown: true, // Accept from anyone (with PoW)
ephemeral: false, // Persist to database
approvable: true, // Recipient can accept/reject
requires_subscription: false, // No subscription required
deliver_subject: false, // Direct to audience
}MSG Action:
Behavior {
broadcast: false, // Send to conversation participants
allow_unknown: false, // Must be conversation member
ephemeral: false, // Persist to database
approvable: false, // No approval needed
requires_subscription: true, // Must be subscribed to conversation
deliver_subject: false, // Uses subscription delivery
}PRES (Presence) Action:
Behavior {
broadcast: true, // Send to connections
allow_unknown: false, // Must be connected
ephemeral: true, // DO NOT persist
approvable: false, // No approval needed
requires_subscription: false, // No subscription required
deliver_subject: false, // Direct broadcast
}Hook System
Hooks allow custom logic at different points in the action lifecycle:
struct Hooks {
on_create: Option<HookFn>, // Called when action is created locally
on_receive: Option<HookFn>, // Called when action is received from federation
on_accept: Option<HookFn>, // Called when approvable action is accepted
on_reject: Option<HookFn>, // Called when approvable action is rejected
}Hook Use Cases
on_create:
- Validate business logic
- Generate related actions
- Update statistics
on_receive:
- Trigger notifications
- Update local state
- Forward to WebSocket clients
on_accept:
- Establish connection (CONN)
- Add to subscriber list (SUBS)
- Grant permissions
on_reject:
- Clean up pending state
- Notify sender of rejection
Hook Example: CONN Action
on_create:
# Validate PoW nonce
verify_proof_of_work(action.nonce)
# Store as pending connection
store_pending_connection(action)
on_receive:
# Notify user of connection request
send_notification(action.audience, "New connection request")
# Forward to WebSocket
forward_to_websocket(action)
on_accept:
# Create bidirectional connection
create_connection(action.issuer, action.audience)
# Update follower counts
update_connection_counts()
on_reject:
# Remove pending connection
remove_pending_connection(action.id)
# Optionally notify senderPermissions
Define who can perform which operations:
struct Permissions {
create: PermissionRule, // Who can create this action type
read: PermissionRule, // Who can read actions of this type
delete: PermissionRule, // Who can delete (owner only typically)
}Permission Rules
| Rule | Description |
|---|---|
Owner |
Only the action issuer |
Audience |
The action’s audience field |
Connected |
Mutually connected users |
Follower |
Users who follow the issuer |
Verified |
Any authenticated user |
Public |
Anyone, including unauthenticated |
Key Pattern
The key_pattern determines how actions are stored and whether they can be overridden:
| Action Type | Key Pattern | Override Behavior |
|---|---|---|
| POST | {iss}:{id} |
Each post is unique |
| CMNT | {iss}:{p}:{id} |
Each comment is unique (p = parent) |
| REACT | {iss}:{sub} |
One reaction per user per subject |
| FLLW | {iss}:{aud} |
One follow per user per target |
| CONN | {iss}:{aud} |
One connection request per pair |
Override example:
User reacts to post with LIKE
→ Stored at key: "alice.example.com:a1~post123"
User changes reaction to LOVE
→ Same key: "alice.example.com:a1~post123"
→ Previous LIKE is REPLACED by LOVEExample Definitions
POST Definition
ActionDefinition {
action_type: "POST",
version: 1,
metadata: Metadata {
display_name: "Post",
description: "A public post or status update",
category: Category::Content,
},
subtypes: vec!["IMG", "VID", "LNK", "TXT"],
fields: vec![
FieldDef { name: "c", field_type: String, required: true },
FieldDef { name: "a", field_type: Array, required: false },
FieldDef { name: "vis", field_type: String, required: false },
FieldDef { name: "f", field_type: String, required: false },
],
behavior: Behavior {
broadcast: true,
allow_unknown: false,
ephemeral: false,
approvable: false,
requires_subscription: false,
deliver_subject: false,
},
hooks: Hooks {
on_create: Some(post_on_create),
on_receive: Some(post_on_receive),
on_accept: None,
on_reject: None,
},
permissions: Permissions {
create: PermissionRule::Owner,
read: PermissionRule::ByVisibility,
delete: PermissionRule::Owner,
},
key_pattern: "{iss}:{id}",
}CMNT Definition
ActionDefinition {
action_type: "CMNT",
version: 1,
metadata: Metadata {
display_name: "Comment",
description: "A comment on another action",
category: Category::Content,
},
subtypes: vec![],
fields: vec![
FieldDef { name: "c", field_type: String, required: true },
FieldDef { name: "p", field_type: String, required: true }, // Parent required
FieldDef { name: "a", field_type: Array, required: false },
],
behavior: Behavior {
broadcast: false,
allow_unknown: false,
ephemeral: false,
approvable: false,
requires_subscription: false,
deliver_subject: true, // Deliver to parent owner
},
hooks: Hooks {
on_create: Some(cmnt_on_create),
on_receive: Some(cmnt_on_receive),
on_accept: None,
on_reject: None,
},
permissions: Permissions {
create: PermissionRule::Connected,
read: PermissionRule::ByVisibility,
delete: PermissionRule::Owner,
},
key_pattern: "{iss}:{p}:{id}",
}CONV Definition
ActionDefinition {
action_type: "CONV",
version: 1,
metadata: Metadata {
display_name: "Conversation",
description: "A group conversation",
category: Category::Communication,
},
subtypes: vec![],
fields: vec![
FieldDef { name: "c", field_type: Object, required: true }, // {name, participants}
],
behavior: Behavior {
broadcast: false,
allow_unknown: false,
ephemeral: false,
approvable: false,
requires_subscription: false,
deliver_subject: false,
},
hooks: Hooks {
on_create: Some(conv_on_create), // Creates SUBS for participants
on_receive: Some(conv_on_receive),
on_accept: None,
on_reject: None,
},
permissions: Permissions {
create: PermissionRule::Verified,
read: PermissionRule::Subscriber,
delete: PermissionRule::Owner,
},
key_pattern: "{iss}:{id}",
}DSL Validation
When an action is created or received, it’s validated against its DSL definition:
validate_action(action, definition):
1. Check required fields exist
2. Validate field types
3. Run schema validation
4. Check behavior constraints:
- If requires_subscription: verify sender is subscribed
- If !allow_unknown: verify sender is known (following/connected)
- If approvable: set initial status to pending
5. Return validation resultExtending with New Action Types
To add a new action type:
- Define the ActionDefinition with all fields, behavior, and hooks
- Register the definition in the action registry
- Implement hooks for custom behavior (optional)
- Add client support for creating/displaying the action
Example: Adding a new POLL action type:
ActionDefinition {
action_type: "POLL",
version: 1,
metadata: Metadata {
display_name: "Poll",
description: "A poll with voting options",
category: Category::Content,
},
subtypes: vec![],
fields: vec![
FieldDef { name: "c", field_type: Object, required: true }, // {question, options, expires}
],
behavior: Behavior {
broadcast: true,
allow_unknown: false,
ephemeral: false,
approvable: false,
requires_subscription: false,
deliver_subject: false,
},
hooks: Hooks {
on_create: Some(poll_on_create),
on_receive: Some(poll_on_receive),
on_accept: None,
on_reject: None,
},
permissions: Permissions {
create: PermissionRule::Verified,
read: PermissionRule::ByVisibility,
delete: PermissionRule::Owner,
},
key_pattern: "{iss}:{id}",
}See Also
- Actions & Action Tokens - Overview of the action system
- Action Delivery - How actions are distributed
- ABAC Permissions - Permission system