Nodlin Starlark Language Extensions

This document describes the Nodlin-specific extensions to the Starlark language. This supplements the standard Starlark syntax documentation available at:
- https://github.com/bazelbuild/starlark/blob/master/spec.md
- https://github.com/google/starlark-go/blob/master/doc/spec.md
Overview
Nodlin extends Starlark with specific types and methods for interacting with nodes, relationships, actions, and events within the Nodlin environment. These extensions enable scripts to create workflows, manage actions, link nodes, and respond to events.
Pre-declared Variables
The following variables are pre-declared in every script execution and are reserved keywords:
C or context (NodeContext)
Provides details about the current request context.
Properties:
C.userID- Tuple of (userid, domain) for the user making the request
Methods:
C.checkpoint()- Creates a new checkpoint in the flow (no parameters)C.newNodeID()- Generates a new random node ID (no parameters)- Returns: String containing new node ID
C.newNodeVersion()- Generates a new random node version ID (no parameters)- Returns: String containing new version ID
N or node (Node)
Represents the current recorded version of the node (prior to any update). This is read-only and cannot be changed.
Properties:
N.nodeID- The unique identifier of the nodeN.nodeType- The FQN type of the nodeN.nodeSubType- The subtype of the node (if specified)N.label- The user-defined label for the nodeN.alias- The alias for the nodeN.relatedorN.R- The relationships from this node (see Relationships)N.data- The data properties of the node (NodePayload)N.<property>- Direct access to any data property by name
Methods:
- See Node Methods section below
O or operation (Action or Event)
Represents the operation that triggered script execution (either an Action or Event).
Common Properties:
O.name- Name of the action or eventO.nodeID- The node IDO.nodeType- The node typeO.data- The payload data
Action-specific Properties:
O.graphID- The graph IDO.domainID- The domain ID
Event-specific Properties:
O.domain- Source domainO.graphID- Source graph IDO.fromNode- Source node IDO.fromType- Source node typeO.overRelation- The relationship over which the event was triggered
Methods:
O.isActionName(name)- Check if this is an action with the specified name- Parameters:
name(string) - Action name to check - Returns: Boolean
- Parameters:
O.isEventName(name)- Check if this is an event with the specified name- Parameters:
name(string) - Event name to check - Returns: Boolean
- Parameters:
V or value (Node)
Represents the result node that will be recorded after successful execution. It is initialized with:
- For actions: the user-provided payload
- For events: a copy of the current node that can be modified
Special Properties (can be set on value):
value.image- Data URI or SVG element to displayvalue.sizeX- Width in pixelsvalue.sizeY- Height in pixelsvalue.label- Short label descriptionvalue.summary- Description for tooltips and book viewvalue.help- Help text specific to this nodevalue.hasError- Boolean indicating input/validation errorvalue.alias- Alias for the node (used innamedreferences)value.hidden- Boolean to hide the node by default
All other properties and methods are the same as Node (N).
named (Dictionary)
Dictionary of nodes indexed by their user-defined names (aliases). Only includes directly related nodes and the current node.
Usage:
# Check if a named node exists
if 'taskNode' in named:
total = total + named['taskNode'].effort
# Iterate through all named nodes
for name in named:
print(name, named[name])
# Access node properties
myNode = named['expense']
amount = myNode.amount
values (Dictionary)
Dictionary containing the evaluated values from expression nodes. For expression nodes, the value property is stored as JSON - this dictionary provides the unmarshalled values.
Usage:
# Access an expression value
if 'credit' in values:
budget = budget + values['credit']
Node Methods
Methods available on any Node object (N, V, or nodes from relationships):
hasOne(relation, [type])
Returns exactly one related node, or fails if zero or multiple nodes exist.
Parameters:
relation(string, required) - Relationship nametype(string, optional) - Node type filter
Returns: Node object or raises error
Examples:
# Get one node over a relationship (any type)
payment = mortgage.hasOne("has_schedule")
# Get one node of specific type over relationship
payment = mortgage.hasOne("has_schedule", "paymentSchedule")
setName(name)
Sets the alias/name for the node (stored in __name property).
Parameters:
name(string, required) - Alphanumeric name, must start with letter, underscores allowed
Returns: None
Example:
value.setName("myTaskNode")
linkToNewNode(...)
Creates a new node and establishes a link from the current node.
Parameters (for scripts):
scriptID(string, optional) - Specific script version IDscriptFQN(string, optional) - Fully qualified script namepayload(dict, optional) - Initial data for the new noderelation(string, optional) - Relationship name (stored in metadata for scripts)label(string, optional) - User-defined label for the relationshipreverse(bool, optional) - Create relationship in reverse direction (default: False)weight(int, optional) - Relationship display priority weightreverseRelation(string, optional) - Relationship name for reverse link (scripts only)
Parameters (for internal types):
FQN(string, optional) - Fully qualified internal node type namepayload(dict, optional) - Initial data for the new noderelation(string, optional) - Full relationship FQNlabel(string, optional) - User-defined labelreverse(bool, optional) - Create relationship in reverse directionweight(int, optional) - Relationship display priority weight
Returns: String containing the new node ID
Examples:
# Create and link to a script node
newID = node.linkToNewNode(
scriptFQN="group.owner.domain.package.task.v1",
label="Sub-task",
payload={"name": "My Task"}
)
# Create with reverse relationship
newID = node.linkToNewNode(
scriptFQN="group.owner.domain.expense",
label="Expense",
reverse=True
)
# Create internal type node
newID = node.linkToNewNode(
FQN="agt_core_all_internalTest_task",
label="Internal Task"
)
link(...)
Creates or removes a link between two existing nodes.
Parameters:
fromNodeID(string, optional) - Source node ID (defaults to current node)fromFQN(string, optional) - Source node type (defaults to script type)toNodeID(string, optional) - Target node ID (defaults to current node)toNodeFQN(string, optional) - Target node type (defaults to script type)relation(string, optional) - Relationship namelabel(string, optional) - User-defined labelreverse(bool, optional) - Create in reverse direction (default: False)remove(bool, optional) - Remove existing relationship (default: False)weight(int, optional) - Relationship display priority weight
Returns: None
Example:
# Create link after checkpoint
newID = node.linkToNewNode(scriptFQN=TASK_TYPE, label="New Task", reverse=True)
C.checkpoint()
node.link(toNodeID=newID, label="depends on")
# Remove a link
node.link(toNodeID=targetID, remove=True)
actionNode(...)
Performs an action on a node (create, delete, or custom action).
Parameters:
action(string, required) - Action name (e.g., “create”, “delete”)nodeid(string, required for non-create) - Target node IDnodeVersion(string, optional) - Current version of the nodenewNodeVersion(string, optional) - New version ID to assignscriptID(string, optional) - Script version ID (for script nodes)scriptFQN(string, optional) - Script FQN (for script nodes)FQN(string, optional) - Node type FQN (for internal types)payload(dict, optional) - Action payload
Returns: String containing the node ID
Example:
# Delete a node
node.actionNode(action="delete", nodeid="abc123", nodeVersion="v1")
# Create a new node with specific action
nodeID = node.actionNode(
action="create",
nodeid=C.newNodeID(),
scriptFQN="group.owner.domain.task",
payload={"name": "New Task"}
)
getActions()
Retrieves all actions associated with the node.
Parameters: None
Returns: UserActionList (iterable collection) or None
Example:
actions = N.getActions()
if actions:
for action in actions:
print(action.actionID, action.comment, action.state)
getFYIs()
Retrieves all FYI (For Your Information) items for the node.
Parameters: None
Returns: List of FYI items or None
Example:
fyis = N.getFYIs()
if fyis:
for fyi in fyis:
print(fyi)
createAction(...)
Creates a new user action for a node.
Parameters:
nodeid(string, optional) - Node ID (defaults to current node)comment(string, optional) - Action commentpriority(int, optional) - Priority: 1 (high), 2 (medium), 3 (low). Default: 3forDate(Time, optional) - Target completion date (defaults to now)successCondition(string, optional) - Completion condition (default: “ALL”)
Returns: String containing the new action ID
Example:
actionID = node.createAction(
nodeid=newNodeID,
comment="Review and approve",
priority=1,
forDate=time.parse_duration("48h") + time.now()
)
createAssignment(...)
Creates an assignment for an existing action.
Parameters:
nodeid(string, optional) - Node ID (defaults to current node)actionid(string, required) - The action ID to assignuser(tuple, optional) - User tuple: (userid, domain)group(tuple, optional) - Group tuple: (groupid, domain)
Note: Either user or group must be specified, but not both.
Returns: String containing the new assignment ID
Example:
# Create action first
actionID = node.createAction(nodeid=nodeID, comment="Review")
C.checkpoint()
# Then create assignment
assignmentID = node.createAssignment(
nodeid=nodeID,
actionid=actionID,
user=("john.doe", "example.com")
)
assignNewAction(...)
Creates both an action and an assignment in one call (with automatic checkpoint).
Parameters:
nodeid(string, optional) - Node ID (defaults to current node)comment(string, optional) - Action commentpriority(int, optional) - Priority: 1 (high), 2 (medium), 3 (low). Default: 3forDate(Time, optional) - Target completion datesuccessCondition(string, optional) - Completion condition (default: “ALL”)user(tuple, optional) - User tuple: (userid, domain)group(tuple, optional) - Group tuple: (groupid, domain)
Returns: Tuple of (actionID, assignmentID)
Example:
actionID, assignmentID = node.assignNewAction(
comment="Approve expense",
priority=1,
user=("manager", "company.com")
)
getAffordances()
Returns list of possible relationships (affordances) for the node.
Parameters: None
Returns: List of Affordance objects or None
Example:
affordances = node.getAffordances()
if affordances:
for aff in affordances:
print(aff.overRelation, aff.toType)
setAffordance(...)
Adds a possible relationship to the node’s spec (the typical relationships). Nodes in nodlin can be connected to any other node, but highlighting the typical relationships (where the node may be dependent on information in related node) prioritises the display and creation of these nodes and relationships.
Parameters:
overRelation(string, optional) - Relationship name from this node.toType(string, optional) - Target node type FQN. If not specified thentoSubTypemust be specified (script FQN).toSubType(string, optional) - Target node subtypeinverseRelation(string, optional) - Reverse relationship name.label(string, optional) - Relationship labelinverseLabel(string, optional) - Reverse relationship label
Note: Either toType or toSubType must be specified. If toType is not specified, it is assumed to be a script type
node.
If relation (overRelation or inverseRelation) is not a fully qualified relationship name, then its inferred to be relationship for current node type.
Note that the new node created will have no payload (just defaulted properties from execution of the target type code or script). If you require to create the new node with a payload, then this must be created in the script.
Having setAffordance in the script (ie, specific to the instance of the node) allows you to provide context specific affordances. For example, if only 1 instance of a connected node type is allowed, then you can avoid setting the affordance if one instance of a connected node already exists.
Returns: The created Affordance object
Example:
# Define possible relationship to another script
aff = node.setAffordance(
overRelation="hasDependency",
toSubType="taskScript",
label="Depends On"
)
# Define relationship to internal type
aff = node.setAffordance(
overRelation="hasTask",
toType="agt_core_all_task",
inverseRelation="taskOwner"
)
Relationships
Access relationships using: node.related.<relationship>.<type> or node.R.<relationship>.<type>
Accessing Relationships
# Get all nodes of a specific type over a relationship
tasks = N.related.depends_on.task
# Check if a relationship exists
if 'expense' in N.related:
expenses = N.related.expense
# Iterate through all relationships
for relation in N.related:
print("Relationship:", relation.hasName)
for nodeType in relation:
nodes = nodeType # This is a NodeList
Relationship Naming
Relationships can be referenced by:
- Formal FQN: e.g.,
agr_core_all_workflowAgent_next - Short form: e.g.,
next(the reference part of the FQN) - CamelCase short: e.g.,
openQuestionforagr_core_all_workflowAgent_open_question - User label: The exact user-defined label (case-sensitive)
Methods on RelatedTypes
all()
Returns list of all nodes across all types for a relationship.
Example:
# Get all related nodes over a relationship (any type)
allNodes = mortgage.R.has_schedule.all()
# Get all relationships and types
allRelated = mortgage.R.all()
hasOne([type])
Returns exactly one node, errors if zero or multiple.
Example:
# One node of any type
schedule = mortgage.R.has_schedule.hasOne()
# One node of specific type
schedule = mortgage.R.has_schedule.paymentSchedule.hasOne()
Action Properties
When O (operation) is an Action, the following properties are available:
Via . accessor:
O.actionIDO.activeO.commentO.priorityO.forDateO.stateO.successConditionO.completedO.createdAtO.updatedAtO.startedAtO.completedAtO.assignments- List of assignment objectsO.createdBy
UserAction Methods:
action.allUsers()- Returns list of all users involvedaction.allGroups()- Returns list of all groups involved
Assignment Properties
Each assignment in action.assignments has:
assignment.assignmentIDassignment.activeassignment.assignedAtassignment.assigneeassignment.assignedToassignment.assignedToGroup
Affordance Properties
Affordance objects have the following properties:
affordance.overRelation- Relationship name from sourceaffordance.toType- Target node typeaffordance.toSubType- Target node subtypeaffordance.inverseRelation- Reverse relationship nameaffordance.label- Relationship labelaffordance.inverseLabel- Reverse relationship label
Standard Starlark Libraries
The following standard libraries are available:
json
JSON encoding/decoding operations.
import json
data = json.encode({"key": "value"})
obj = json.decode('{"key": "value"}')
math
Mathematical functions.
import math
result = math.sqrt(16)
result = math.ceil(4.3)
time
Time and duration operations.
Functions:
time.now()- Current timetime.parse_duration(d)- Parse duration string (e.g., “24h”, “30m”)time.from_timestamp(sec, nsec)- Create time from Unix timestamptime.is_valid_timezone(loc)- Check if timezone is validtime.parseTime(timeStr, format, location)- Parse time stringtime.time(year, month, day, hour, minute, second, nanosecond, location)- Create time
Duration units: “ns”, “us” (or “ยตs”), “ms”, “s”, “m”, “h”
Examples:
# Current time
now = time.now()
# Time in 24 hours
tomorrow = time.parse_duration("24h") + time.now()
# Time in 2 days and 3 hours
future = time.parse_duration("51h") + time.now()
# Access time properties
print(now.month, now.day, now.year)
Nodlin-specific Libraries
webform
Create web forms for node UI.
# Create form
form = webform.WebForm()
# Add user input control
userOpts = webform.nodlinUserOpts(help="Select user", singleContactOnly=False)
form.addUserInput('responsible', 'Responsible', userOpts)
# Access selected values
if 'responsible' in value:
for u in value.responsible['users']:
print("USER %s in domain %s" % (u['userid'], u['domain']))
for g in value.responsible['groups']:
print("GROUP %s in domain %s" % (g['group'], g['domain']))
gridder
Grid layout utilities for SVG generation.
color
Color manipulation utilities.
font
Font utilities for text rendering.
textbox
Text box utilities for SVG text.
image
Image manipulation utilities.
datetime
Additional date/time utilities.
Best Practices
Checkpoints
Use checkpoints to ensure operations complete successfully before dependent operations:
# Create node
newID = node.linkToNewNode(scriptFQN="...", label="Task")
# Checkpoint before creating action
C.checkpoint()
# Create action (requires node to exist)
actionID = node.createAction(nodeid=newID, comment="Review")
# Checkpoint before assignment
C.checkpoint()
# Create assignment (requires action to exist)
node.createAssignment(nodeid=newID, actionid=actionID, user=("user", "domain"))
Error Handling
Use fail() to abort script execution:
if not hasattr(value, 'requiredField'):
fail("requiredField is required")
Default Values
Use getattr() for safe property access with defaults:
# Safe access with default
total = getattr(expense, 'value', 0)
# Dictionary safe access
creditAmount = values.get("credit", 0)
String Formatting
Use string interpolation for output:
x = 0.5
print("Value: %g" % x) # Compact form
print("Value: %f" % x) # Full float
print("Name: %s" % name) # String
Raw Strings
Use raw strings for SVG and special characters:
svg = r'<svg xmlns="http://www.w3.org/2000/svg">...</svg>'
Parameterized Strings
Use .format() for template strings:
template = """
<svg viewBox="0 0 {width} {height}">
<text>{label}</text>
</svg>
"""
params = {'width': 100, 'height': 50, 'label': 'My Label'}
value.image = template.format(**params)
Reserved Properties
The following properties have special meanings on nodes and should be set appropriately:
__name- User-defined node name/alias__affordances- List of possible relationships (set viasetAffordance())value- For expression nodes, contains evaluated result as JSONcondition- For condition nodes, contains boolean result
Types Reference
Node Types
Node- Represents a node in the graphNodeContext- Execution context informationNodePayload- User-defined data on a nodeNodeList- List of nodes
Relationship Types
Relationships- Dictionary of relationships from a nodeRelatedTypes- Dictionary of node types over a relationshipAffordance- Possible relationship definition
Operation Types
Action- User-initiated operationEvent- System-triggered operation
Action/Assignment Types
UserAction- Action assigned to users/groupsUserActionList- Collection of actionsAssignment- Assignment of action to user or group
Additional Notes
Immutability: The
N(node) andC(context) objects are frozen and cannot be modified. OnlyV(value) can be modified.Case Sensitivity: Relationship names are case-sensitive when using the full form. CamelCase conversion is applied for short forms.
FQN Format: Fully Qualified Names (for internal node types) follow the pattern:
<type>_<domain>_<user/group>_<agentName>_<name>
type can be one of ‘agt’ to represent node type, or agr for relation.
Examples:
- agt_core_all_expressionAgent_chart (the ‘chart’ node type)
- agr_core_all_expressionAgent_chart_for (relation from ‘chart’ to node containing detail to chart)
- agt_master_admin_openai_requestWithSchema (node type for an openai request with a schema)
- agt_core_all_basicTypeAgent_basicNote (node type for a basic note)
- agt_core_all_expressionAgent_expression (node type containing an expression)
- Script FQN: Fully Qualified Names (for script node types) follow the pattern:
<user|group>.<user or group name>.<domain>.<package>.<nodeType>[.<version>]
Examples:
- user.johnha.master.togaf.workPackage
- user.johnha.master.evidenceGraph.justification.prodVersion
- Script Relationships: For script-to-script relationships, the formal relationship is always
agt_core_all_scriptAgent_scriptNodeor short formdepends_on, with custom names stored in metadata.
Note that nodlin allows for agents to be defined that provide the logic for managing a group of nodes. The python (starlark) scripting
agent is just one such example and conforms to the same interface as other agents. All script nodes in nodlin connect
to other script nodes over the depends_on relationship. The python script agent however records additional detail
in metadata on the node and relationship to allow it to function like other external and internal agents.
Node Caching: Only directly related nodes are cached. Reference nodes (placeholder nodes) are created for nodes not in the cache.
Value Storage: Changes to
V/valueare only persisted if the script completes successfully without errors.