new-collection-object
Boilerplate and patterns for creating new Collection-managed runtime objects (Streams, Interfaces, Protocol components). Use when adding a new managed object type.
Install
mkdir -p .claude/skills/new-collection-object && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14741" && unzip -o skill.zip -d .claude/skills/new-collection-object && rm skill.zipInstalls to .claude/skills/new-collection-object
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Boilerplate and patterns for creating new Collection-managed runtime objects (Streams, Interfaces, Protocol components). Use when adding a new managed object type.About this skill
new-collection-object
Create a new Collection-managed runtime object in the OpenWatt codebase. This skill generates the boilerplate for objects managed by the Collection system (Streams, Interfaces, Protocol components, etc.).
Usage
When the user asks to create a new Collection-managed object, use this skill to guide the implementation. Ask the user for:
- Object type: Stream, Interface, Protocol client/server, or other
- Module name: e.g., "mqtt", "modbus", "tcp", "serial"
- Class name: e.g., "MQTTBroker", "HTTPServer", "CANInterface"
- Properties: List of properties with types and descriptions
- Base class: Usually Stream, BaseInterface, or BaseObject
Boilerplate Patterns
1. File Structure
All Collection-managed objects follow this structure:
module [layer].[category].[module];
// Standard imports
import urt.lifetime;
import urt.log;
import urt.string;
import urt.time;
// ... other urt imports as needed
import manager;
import manager.base;
import manager.collection;
import manager.console;
import manager.plugin;
// Layer-specific imports
public import [layer].[category]; // e.g., router.stream, router.iface
nothrow @nogc:
// Enums/structs as needed
enum YourEnum : ubyte
{
Value1,
Value2,
}
// Main class
class YourObject : BaseClass
{
__gshared Property[N] Properties = [ Property.create!("prop1", prop1)(),
Property.create!("prop2", prop2)(),
/* ... */ ];
nothrow @nogc:
alias TypeName = StringLit!"type-name";
this(String name, ObjectFlags flags = ObjectFlags.None)
{
super(collection_type_info!YourObject, name.move, flags);
}
// Properties...
[property implementations]
// API...
override bool validate() const pure
{
// Validate configuration
return true;
}
override CompletionStatus startup()
{
// Initialize resources
return CompletionStatus.Complete;
}
override CompletionStatus shutdown()
{
// Clean up resources
return CompletionStatus.Complete;
}
override void update()
{
// Per-frame processing
}
private:
// Member variables
}
// Module class
class YourModule : Module
{
mixin DeclareModule!"[layer].[module]";
nothrow @nogc:
Collection!YourObject objects;
override void init()
{
g_app.console.registerCollection("/[category]/[type]", objects);
}
override void update()
{
objects.update_all();
}
}
2. Property Declaration Patterns
The Properties Array (REQUIRED):
__gshared Property[N] Properties = [ Property.create!("prop-name", prop_name)(),
Property.create!("other-prop", other_prop)(),
/* exactly N properties */ ];
Mutually Exclusive Properties:
When properties represent different modes/options where only one should be active, track which is set internally rather than validating mutual exclusion:
void mode_a(TypeA value)
{
if (_mode_a == value)
return;
_mode_a = value;
_active_mode = Mode.A; // Track which mode is active
restart();
}
void mode_b(TypeB value)
{
if (_mode_b == value)
return;
_mode_b = value;
_active_mode = Mode.B; // Overwrite previous mode
restart();
}
// In validate(), check that one mode is set
override bool validate() const
{
return _active_mode != Mode.None;
}
Property Getter/Setter Patterns:
Simple value type:
// Getter
ushort port() const pure
=> _port;
// Setter
void port(ushort value)
{
if (_port == value)
return; // early out is there is an advantage
_port = value;
restart(); // Trigger re-initialization if required
}
String/complex type:
// Getter
ref const(String) device() const pure
=> _device;
// Setter with validation (returns StringResult)
StringResult device(String value)
{
if (!value)
return StringResult("device cannot be empty");
if (_device == value)
return StringResult.success;
_device = value.move;
restart();
return StringResult.success;
}
Enum type:
Parity parity() const pure
=> _parity;
void parity(Parity value)
{
if (_parity == value)
return;
_parity = value;
restart();
}
Multiple overloads (type conversion):
void port(WellKnownPort value)
=> port(cast(ushort)value);
void port(ushort value)
{
if (_port == value)
return;
_port = value;
restart();
}
ObjectRef property (reference to another Collection object):
// Getter
inout(Stream) stream() inout pure
=> _stream;
// Setter
StringResult stream(Stream value)
{
if (!value)
return StringResult("stream cannot be null");
if (_stream is value)
return StringResult.success;
_stream = value;
restart();
return StringResult.success;
}
private:
ObjectRef!Stream _stream;
3. State Machine Methods
validate() - Configuration Validation
Return true if configuration is valid:
override bool validate() const pure
{
// Check required properties are set
if (_device.empty)
return false;
// Check valid ranges/combinations
if (_baud_rate < 300 || _baud_rate > 115200)
return false;
// Check dependencies
if (_stream.get() is null)
return false;
return true;
}
startup() - Resource Initialization
Initialize resources. Return:
CompletionStatus.Complete- SuccessCompletionStatus.Continue- Still initializing, call again next frameCompletionStatus.Error- Failed, will retry with exponential backoff
override CompletionStatus startup()
{
// Async resolution example
if (_host)
{
AddressInfo addrInfo;
addrInfo.family = AddressFamily.ipv4;
AddressInfoResolver results;
get_address_info(_host, null, &addrInfo, results);
if (!results.next_address(addrInfo))
return CompletionStatus.Continue; // Not ready yet
_address = addrInfo.address;
}
// Resource creation with error handling
Result r = create_resource(_handle);
if (!r)
{
writeError(type, " '", name, "' - failed to create resource: ", r);
return CompletionStatus.Error; // Will retry with backoff
}
// Initialize state
_initialized = true;
writeInfo(type, " '", name, "' started successfully");
return CompletionStatus.Complete;
}
shutdown() - Resource Cleanup
Clean up all resources. Must not fail. Called repeatedly while in State.Stopping (NOT update()).
Return CompletionStatus.Continue to continue shutdown asynchronously:
override CompletionStatus shutdown()
{
// For async/cancellable resources, request cancellation and wait
if (has_pending_operations())
{
request_cancellation();
poll_for_completion();
if (still_waiting())
return CompletionStatus.Continue; // Will be called again
}
// Close handles/sockets
if (_handle != invalid_handle)
{
close_handle(_handle);
_handle = invalid_handle;
}
// Clear state
_initialized = false;
// Destroy if temporary
if (_flags & ObjectFlags.Temporary)
destroy();
return CompletionStatus.Complete;
}
update() - Per-Frame Processing
Called every frame while Running:
override void update()
{
// Check connection status
if (!is_connected())
{
restart();
return;
}
// Process incoming data
ubyte[4096] buffer;
ptrdiff_t received = read(buffer);
if (received > 0)
process_data(buffer[0..received]);
// Update internal state
_last_activity = getTime();
}
4. Constructor Pattern
For Streams (with StreamOptions):
this(String name, ObjectFlags flags = ObjectFlags.None, StreamOptions options = StreamOptions.None)
{
super(collection_type_info!YourStream, name.move, flags, options);
}
For Interfaces (with InterfaceOptions):
this(String name, ObjectFlags flags = ObjectFlags.None, InterfaceOptions options = InterfaceOptions.None)
{
super(collection_type_info!YourInterface, name.move, flags, options);
}
For other BaseObject subclasses:
this(String name, ObjectFlags flags = ObjectFlags.None)
{
super(collection_type_info!YourObject, name.move, flags);
}
5. TypeName Requirement
Every Collection-managed class needs:
alias TypeName = StringLit!"type-name";
This determines:
- Console command path:
/stream/type-name,/interface/type-name - Default object naming
- Type identification in logs
Examples:
StringLit!"tcp"→/stream/tcp-clientStringLit!"serial"→/stream/serialStringLit!"modbus"→/interface/modbusStringLit!"mqtt-broker"→/protocol/mqtt/broker
6. Module Class Pattern
class YourModule : Module
{
mixin DeclareModule!"layer.module";
nothrow @nogc:
// Collections
Collection!YourStream streams;
Collection!YourInterface interfaces;
// Non-collection objects (if any)
Map!(const(char)[], YourClient) clients;
override void init()
{
// Register collections with console
g_app.console.registerCollection("/stream/your-type", streams);
g_app.console.registerCollection("/interface/your-type", interfaces);
}
override void pre_update()
{
// Update all collection objects
streams.update_all();
interfaces.update_all();
}
override void update()
{
// Update non-collection objects
foreach(client; clients.values)
client.update();
}
override void post_update()
{
// Post-processing if needed
}
}
7. Module Registration
Add to `src/ma
Content truncated.