| name | model-building |
| description | Building MobX models with proper architecture, BaseModel patterns, Store usage, and validation |
Model Building Skill
Quick Reference
ALWAYS use #ui_code_tools to scaffold new models - never create manually
Architecture Essentials
- Every model extends
BaseModel→StoreModel(lifecycle metadata + MobX) - Always create records via Store:
Store.asset.create()- NEVERnew Model() - Each model has an
APIStoreregistered inStore.tsfor caching and hydration
File Structure
packages/models/src/models/<entity>/
├── _constants/ # Enums and status values
├── model/
│ ├── <Entity>BaseModel.ts # Pure API fields with @attr
│ └── <Entity>Model.ts # Computed logic and helpers
├── services/ # Complex workflows
└── constants.ts # Re-export constants
BaseModel Rules
- Only
@attrdecorated fields that mirror the backend API - Separate editable fields from
{readOnly: true}joined data - Use narrowest type:
string,number,decimal,uuid,json - Mark joins as
readOnlyso serialization skips them
// BaseModel - Pure API data only
export class AssetBaseModel extends BaseModel {
@attr("string") name: string = "";
@attr("uuid") organization_id: string = "";
@attr("number") status: number = 1;
@attr("json", { readOnly: true }) organization?: Organization;
}
Derived Model Pattern
- Add computed getters, validation, navigation helpers
- Call
this.addObserve(this)in constructor for MobX tracking - For nested JSON: create class extending
ValidationClass, useaddObserve(child, this, "field")
export class AssetModel extends AssetBaseModel {
constructor() {
super();
this.addObserve(this);
}
get statusConst(): IConstant {
return findConstant(constants.asset.status, this.status);
}
get statusStr(): string {
return this.statusConst.label;
}
get validationRules() {
return assetValidationRules;
}
}
Loading Data
// Lists
Store.asset.query({ organization_id: "123" });
// Single record
Store.asset.get("asset-id");
// Custom routes
Store.asset.queryRecord("/custom-endpoint");
// Force fresh data
Store.asset.get("id", { skipCache: true });
Mutations
// Update directly - MobX tracks changes
model.name = "Updated";
// Save (auto POST/PUT based on isNew)
await model.save();
// Discard changes
model.rollback();
Constants Pattern
Always create helper getters for constants:
get asset_typeConst(): IConstant {
return findConstant(constants.asset.asset_type, this.asset_type);
}
get asset_typeStr(): string {
return this.asset_typeConst.label;
}
Validation
- Define rules in
<entity>/validation_rules.tsusingValidationRulesType<Model> - Expose via
get validationRules()in derived model - Nested JSON classes can maintain their own rules
- Ask before implementing more detailed validations
Key Principles
- Never call
new Model()directly - use Store methods - Never mutate
_loaded_attributesor_dirtyAttributesmanually - Keep BaseModels pure - no computed logic
- Wire observers on nested JSON immediately after instantiation
- Keep getters cheap and side-effect free - HTTP belongs in services/stores